DCL Key Definition Routines
by
Hunter Goatley
Clyde Digital Systems
The VMS Run-Time Library (VMS RTL) contains a large assortment of commonly needed string manipulation functions (STR$), math functions (MTH$), and various other routines, including some that make the system services easier to call from high-level languages (LIB$). Among the LIB$ routines are some you can call to ask your command language interface (CLI) to perform some functions for you. These include LIB$SET_SYMBOL (define a DCL symbol), LIB$GET_SYMBOL (get the value of a DCL symbol), LIB$SET_LOGICAL (assign logicals from supervisor mode), and routines to delete the symbols and logicals (LIB$DELETE_*).
With VMS version 4.0, user-definable DCL keys came into being. Essentially, the ability to define keys from DCL gives you the ability to tie often-typed DCL commands to a key so that they can be executed literally at the touch of a button. All of the keypad and editing keys can be defined, as well as most of the function keys. The terms “key” and “keypad” are used interchangeably throughout this article to refer to user-definable keys. The general format of the DCL command to define a key is
$ DEFINE/KEY key-name "DCL command to execute"
There are various qualifiers that affect how DCL treats the key definition (echo the command string, terminate the command immediately, etc.). (For a more in-depth look at DEFINE/KEY, see the June 1987 issue of DEC Professional.) To make life more fun, each key can be set up to have a different definition depending on the current key “state.” The default key state is named (what else?) “DEFAULT”. If a key definition is entered without specifying a state (with /STATE=), the key is defined for the current key state. The key will have that definition until the key state is changed ($ SET KEY/STATE=NEWNAME). It didn’t take long for defined DCL keys to invade my way of life under VMS; such things as SHOW USERS and DIR could now be done by pressing one key with one finger. Very handy for lazy typists such as me!
Being a “speed fanatic” (meaning I want such things as logging in done as quickly as possible), I had already written a LOGIN program that my LOGIN.COM executes to set up all of my symbols and define all of my logicals (via the LIB$ routines mentioned above), as well as to perform tasks that normally require the activation of an image (set process name, default protection, etc.). Unfortunately (for me), DEC had not provided LIB$ routines to define DCL keys. In order to DEFINE all of my keys, my wonderfully fast LOGIN program had to call LIB$DO_COMMAND to execute a command procedure that did the key definitions. Needless to say, this slowed my program down some and bugged me (I know, what’s another second or two?). Anyway, not having the LIB$ routines I wanted made me finally sit down and write them myself.
Keypad Definition Internals
Before I talk about my routines, let me discuss how VMS stores keypad definitions. DCL stores all keypad definitions in a process’s P1 space. Each key definition is described by a data structure containing such information as the key name, the equivalence string associated with that key, the key state for which the definition is valid, and more. As with most VMS data structures, the structures are maintained as a doubly-linked list; the first two longwords of each structure point to the previous and next structures in the list. Each key structure is similar to the structures for all DCL symbols; in fact, the same symbolic offsets are used for both types of structures. The offsets are defined in SYS$SYSTEM:DCLDEF.STB and have the prefix SYM_. Any program wishing to use these symbols must be linked with the DCLDEF symbol table.
Figure 1 describes the layout for a key structure under VMS 4.5. Note that some of the fields have meaning for symbol definitions and are unused in keypad definitions. The size of the structure is the total length of all of the components, rounded to the nearest quadword boundary (for better memory allocation).
Keypad definition structure layout
(Symbols defined in SYS$SYSTEM:DCLDEF.STB)
Symbolic offset Offset Meaning
=============== ======== =======
SYM_L_FL 00000000 Forward link in keypad list
SYM_L_BL 00000004 Backward link in keypad list
SYM_L_ORDERED 00000008 Unused
SYM_W_FILELEVEL 0000000C Unused
SYM_W_PROCLEVEL 0000000E Unused (set to -1)
SYM_L_PROCSEQ 00000010 Unused
SYM_W_BLOCKLEVEL 00000014 Unused
SYM_L_BLOCKSEQ 00000016 Unused
SYM_W_SIZE 0000001A Size of keypad definition structure
SYM_B_TYPE 0000001C Structure type (SYM_K_KEYPAD)
SYM_W_FLAGS 0000001D The flags described below (SYM_M_*)
SYM_T_SYMBOL 0000001F Symbol name (ASCIC string)
The remainder of the structure:
WORD - combined lengths of next 3 strings + 4
BYTE - length of IF_STATE string
ASCII if_state string
WORD - length of equivalence string
ASCII equivalence string
BYTE - length of SET_STATE string
ASCII set_state string
SYM_K_KEYPAD 00000004 Symbol is a keypad symbol
SYM_M_ECHO 00000001 /ECHO the command string
SYM_M_TERMINATE 00000002 /TERMINATE the command immediately
SYM_M_STATE 00000004 /SET_STATE - the key should set a state
SYM_M_LOCK 00000008 /LOCK - lock the SET_STATE
SYM_M_ERASE 00000010 /ERASE any characters already entered
Figure 1
CTL$AG_CLIDATA is a system-wide global symbol (defined in SYS$SYSTEM:SYS.STB) that points to the beginning of the memory in a process’s P1 space that can be used by the CLI to store data. DCL defines symbols that are offsets from this location; the symbol we’re interested in is PPD$L_PRC (PPD stands for Process Permanent Data), which points to the PRoCess work area–the area where DCL stores such things as the DCL prompt, the recall command buffer, symbol definitions, and keypad definitions! DCL further defines some symbols as offsets into the PRC area. Again, the symbols we’re interested in are PRC_Q_KEYPAD, PRC_L_CURRKEY, and PRC_L_LASTKEY. (By the way, the symbols I needed for my VAX Professional article on flushing the DCL command buffer are defined in DCLDEFS also. Now I know…)
PRC_Q_KEYPAD is the listhead for the keypad definition list. The quadword contains a forward pointer to the first structure in the list (if present) and a backward pointer to the last structure in the list. If there are no key definitions, both longwords point to PRC_Q_KEYPAD. The key definition structures are sorted alphabetically on two fields: they are sorted primarily by the IF_STATE string and then by key name. (All of the definition structures for a each state are sorted by the state name; the structures within each state are then sorted alphabetically by key name.) When a keypad key is pressed, DCL looks at the current key state and begins searching for the first key defined for that state. Once a key is found, all of the key definitions in that state are searched until the correct entry is found (if it exists at all). The equivalence string is then used as the next DCL command.
Figure 2 shows an actual key definition structure as displayed with the System Dump Analyzer. The only key that has been defined is E1. Note that the unused portions of the structure may contain garbage (depending on what the memory was used for before allocated as a key definition).
$ ! Only the E1 key has been defined
$ DEFINE/KEY E1 "HELP"/terminate/echo/noerase/if_state=CMD
$ ANALYZE/SYSTEM
VAX/VMS System Analyzer
SDA> read sys$system:dcldef ! Read in DCLDEF.STB
SDA> exam ctl$ag_clidata+ppd$l_prc ! Get PRC address
CTL$AG_CLIDATA+008: 7FFE33DC ".3.." ! ...
SDA> define prc = 7ffe33dc ! Define PRC to point there
SDA> exam prc+prc_q_keypad ! Look at forward link
PRC+040: 7FF4A578 "x..." ! ... Points to E1
SDA> exam prc+prc_q_keypad+4 ! Look at backward link
PRC+044: 7FF4A578 "x..." ! ... Also points to E1
SDA> format/type=sym 7ff4a578 ! Format the structure
7FF4A578 SYM_L_FL 7FFE341C ! The forward link
7FF4A57C SYM_L_BL 7FFE341C ! The backward link
7FF4A580 SYM_L_ORDERED 00000000 ! Unused
7FF4A584 SYM_W_FILELEVEL 0000 ! Unused
7FF4A586 SYM_W_PROCLEVEL FFFF ! Unused (set to -1)
7FF4A588 SYM_L_PROCSEQ 00000000 ! Unused
7FF4A58C SYM_W_BLOCKLEVEL 0000 ! Unused
7FF4A58E SYM_L_BLOCKSEQ 0000 ! Unused
7FF4A590 0000 ! Unused
7FF4A592 SYM_W_SIZE 0030 ! Size of this structure
7FF4A594 SYM_B_TYPE 04 ! Type = SYM_K_KEYPAD
7FF4A595 SYM_W_FLAGS 0003 ! Flags=/TERMINATE/NOERASE/ECHO
SYM_W_NONUNIQUE
7FF4A597 SYM_T_SYMBOL "E1" ! The keyname
SDA> exam .;30
FFFF0000 00000000 7FFE341C 7FF4A5A8 .....4.......... 7FF4A578
02000304 00300000 00000000 00000000 ..........0..... 7FF4A588
00005453 494C0004 444D4303 000B3145 E1...CMD..LIST.. 7FF4A598
^ ^ ^ ^
1 2 3 4
4 = Word sum of lengths of next three strings
3 = ASCIC IF_STATE string
2 = ASCIC equivalence command ("LIST")
1 = Length of SET_STATE string (non-existent here)
Figure 2
Actual key definition structure
The two symbols PRC_L_CURRKEY and PRC_L_LASTKEY point to key state names (represented as ASCIC strings). Initially, both locations point to the same ASCIC string: the current keypad state. When a keypad key is pressed, DCL uses the string pointed to by PRC_L_CURRKEY as the current key state by which to find the keypad definition. The reason two pointers are needed is that a key can be defined to change the key state only for the next key, allowing you to have a key that functions much like the GOLD key in EDT. When one of these keys is pressed, the SET_STATE string stored in its definition is made the new key state (PRC_L_CURRKEY points to this new key state name). The next key that is pressed is located by this new state. If the SET_STATE is temporary (the SYM_M_LOCK bit is not set in the SYM_W_FLAGS mask), DCL copies the address at PRC_L_LASTKEY back to PRC_L_CURRKEY after the second key has been pressed. If the SET_STATE is to be locked, the “new” pointer at PRC_L_CURRKEY is copied to PRC_L_LASTKEY, and the process begins all over again. Figure 3 graphically demonstrates what happens when a “GOLD” key is defined.
Assume:
1) Current key state is DEFAULT
2) Two keys have been defined:
$ DEFINE/KEY ENTER "SHOW "/echo/noterminate/set_state=GOLD
$ DEFINE/KEY KP0 "USERS"/echo/terminate/if_state=GOLD
Initially:
PRC_L_LASTKEY --> "DEFAULT" <-- PRC_L_CURRKEY
When ENTER is pressed, the current state becomes "GOLD":
PRC_L_LASTKEY --> "DEFAULT"
PRC_L_CURRKEY --> "GOLD"
After KP0 is pressed, the current state becomes "DEFAULT" again:
PRC_L_LASTKEY --> "DEFAULT" <-- PRC_L_CURRKEY
Figure 3
"GOLD" key example showing the effects on the
pointers to the current key state
All of the DCL key definition routines allocate memory from a process’s P1 space. The system routines EXE$ALLOCATE and EXE$DEALLOCATE are used to allocate and deallocate the memory as needed. The actual DCL routines also use these routines, instead of EXE$ALOP1PROC and EXE$DEAP1. The free P1 memory listhead can be found at PRC_Q_ALLOCREG.
The Routines
After figuring out how DCL handled keypad definitions, I wrote a few routines that could be called from a program to perform the same functions. The bad news (?) about the routines is that they must run in executive mode. The P1 space that is used to store the key definitions is protected from user and supervisor modes, so each routine goes into executive mode to do its work. The routines I wrote are
- HG$DEFINE_KEY — defines a DCL key
- HG$GET_KEYDEF — returns the definition of a DCL key
- HG$DELETE_KEY — deletes a DCL keypad definition
- HG$FIND_KEY — finds the location in the linked list for a key
- HG$SET_KEYSTATE — changes the current key state (and returns old)
Program 1 is made up of these routines. One obvious feature that is missing from these routines is the check for a valid key name. It is possible to use these routines to define keys that don’t really exist (perhaps you can find some use for this). I didn’t feel like writing that routine because I knew that I wasn’t going to try to define a non-existent key. I also leave it up to you to work the routines into a privileged shareable image (if desired) to allow non-privileged users to call these routines.
These routines work “as is” for VMS 4.5. Earlier versions of VMS (prior to VMS 4.4) differ in the layout of the keypad definition structures. For VMS 4.1 the structures are 19 bytes smaller. In HG$DEFINE_KEY and HG$GET_KEYDEF, I have defined the symbol “VMS4_5”; if you are running VMS v4.1, simply remove that symbol definition and the routines will assemble correctly.
The routines presented in Program 1 should be split into separate files and could then be placed in an object library. The commands to do this are
$ DEFINE DCL$KEYLIB disk:[dir] $ LIBRARY/MACRO/CREATE DCLKEY.MLB DCLKEYMACS.MAR $ MACRO DEFINE,DELETE,STATE,FIND,GETDEF $ LIBRARY/OBJECT/CREATE DCLKEY.OLB _*.OBJ $
The LOGIN program
I have included (Program 2) a small version of my LOGIN program to show you how you can use HG$DEFINE_KEY to define your keys from a program. LOGIN also defines symbols and logicals using the LIB$ routines mentioned at the beginning of this article. Because all of the data used is actually stored in the executable image, the routines do extra work to set up string descriptors for each symbol, logical, and key definition to cut down on the amount of disk space needed for the program (a savings of 14 bytes for each pair of symbol and logical definitions is achieved by storing them as ASCIC strings instead of ASCID). The program can be expanded to perform more of the functions your LOGIN.COM is currently doing. For example, calls to system services can be made to set your RMS default protection, turn on privileges, etc.
To build the LOGIN program, execute the following commands:
$ MACRO LOGIN $ LINK LOGIN,DCLKEYS/LIBRARY $ RUN LOGIN
Note that if you don’t have a lot for LOGIN to do, it may not be faster than letting your LOGIN.COM do the work. My LOGIN defines more than 190 logicals, symbols, and keys (as well as setting process name, etc.), and there is quite a difference in the time it takes for me to log in now compared to the old way. Whether or not you find these routines useful, you now have some idea of the behind-the-scenes action of DCL keypad usage.
Biographical Information:
Hunter Goatley, a graduate in Computer Science from Western Kentucky University, is currently working as a programmer/analyst for Clyde Digital Systems, Orem, Utah.
DCLKEYS.MAR
;=============== DCLKEYMACS.MAR
;
; AUTHOR: Hunter Goatley
; Clyde Digital Systems
; 371 East 800 South
; Orem, Utah 84058
; (801) 224-5306
; CREATION DATE: 15-MAY-1987
;
; CHECK_ARGS - Checks the number of arguments passed to a routine.
; If an invalid number of arguments are given, control
; returns to the user with LIB$_WRONUMARG status.
;
.MACRO CHECK_ARGS NUMBER,?LABEL
MOVL #LIB$_WRONUMARG,R0 ; Assume error
CMPW NUMBER,(AP) ; Were there enough?
BEQLU LABEL ; Yes - no sweat
RET ; No - return with error
LABEL: .ENDM CHECK_ARGS
;
; GET_PRC - Places the PRC address in R11.
;
.MACRO GET_PRC
MOVAL G^CTL$AG_CLIDATA,R11 ; Get address of CLI data
MOVL PPD$L_PRC(R11),R11 ; Get address of PRoCess data region
.ENDM GET_PRC
;================ DEFINE.MAR
.TITLE HG$DEFINE_KEY
.IDENT "01-001"
;+
; Function: HG$DEFINE_KEY
;
; Author: Hunter Goatley 15-MAY-1987
;
; Functional description:
;
; HG$DEFINE_KEY performs the same function as the DCL DEFINE/KEY command.
;
; Inputs:
;
; 4(AP) - Descriptor pointing to the keyname to define
; 8(AP) - Descriptor pointing to the key's value
; 12(AP) - Descriptor pointing to /IF_STATE= string
; 16(AP) - Descriptor pointing to /SET_STATE string (0
; if the key does not SET_STATE)
; 20(AP) - Address of longword holding SYM_M_* flags
;
; Outputs:
;
; Status in R0 - SS$_NORMAL, LIB$_INSCLIMEM
;
; Effects:
;
; Defines a DCL key
;
; Calling sequence:
;
; STATUS = HG$DEFINE_KEY ("PF1","SHOW USERS","DEFAULT",,(SYM_M_ECHO) BY REF)
;-
VMS4_5 = 1 ;**************************** Remove this line if under VMS 4.1
.LIBRARY /DCL$KEYLIB:DCLKEY.MLB/
.LINK /SYS$SYSTEM:SYS.STB/
.LINK /SYS$SYSTEM:DCLDEF.STB/
.PSECT _HG$DEFINE_KEY_CODE,EXE,NOWRT,LONG,PIC,SHR
KEY_NAME = 4
EQUIVALENCE = 8
IF_STATE = 12
SET_STATE = 16
FLAGS = 20
WORK_BYTES = 512
.ENTRY HG$DEFINE_KEY,^M<R3,R10,R11>
CHECK_ARGS #5 ; Make sure 5 arguments are present
SUBL2 #WORK_BYTES,SP ; Allocate some space on the stack
MOVL SP,R10 ; R10 --> template
;
; Fill in the key template
;
CLRL SYM_L_FL(R10) ; Clear forward link
CLRL SYM_L_BL(R10) ; Clear backward link
.IF DEFINED VMS4_5
CLRL SYM_L_ORDERED(R10) ; Clear ORDERED link (not used)
CLRW SYM_W_FILELEVEL(R10) ; Clear file level
MNEGW #1,SYM_W_PROCLEVEL(R10) ; Set no procedure level
CLRL SYM_L_PROCSEQ(R10) ; ...
CLRW SYM_W_BLOCKLEVEL(R10) ; ...
CLRL SYM_L_BLOCKSEQ(R10) ; ...
.ENDC
CLRW SYM_W_SIZE(R10) ; Clear size
MOVB #SYM_K_KEYPAD, - ; Set symbol entry type (KEYPAD symbol)
SYM_B_TYPE(R10) ; ...
MOVL FLAGS(AP),R0 ; Get the flags
.IF DEFINED VMS4_5
MOVW (R0),SYM_W_FLAGS(R10) ; ... and set them in the template
.IF_FALSE
MOVB (R0),SYM_B_FLAGS(R10) ; ... and set them in the template
.ENDC
MOVAB SYM_T_SYMBOL(R10),R3 ; Get address in template of key name
MOVL KEY_NAME(AP),R0 ; Get the key name
MOVB (R0),(R3)+ ; Move its length
MOVC3 (R0),@4(R0),(R3) ; Copy the string to the template
MOVW @IF_STATE(AP),R0 ; Get sum of lengths of next 3 strings
ADDW2 @EQUIVALENCE(AP),R0 ; ...
TSTL SET_STATE(AP) ; Was a SET_STATE name given?
BEQLU 10$ ; No - don't try to move the length
ADDW2 @SET_STATE(AP),R0 ; ...
10$: ADDW2 #4,R0 ; ... + 4 (the number of length bytes)
MOVW R0,(R3)+ ; Put sum of lengths of next 3 fields
PUSHL R0 ; Save the length for a minute
MOVL IF_STATE(AP),R1 ; Get address of IF_STATE
MOVB (R1),(R3)+ ; Move length of IF_STATE
MOVC3 (R1),@4(R1),(R3) ; Move IF_STATE string
MOVL EQUIVALENCE(AP),R1 ; Get address of EQUIVALENCE string
MOVW (R1),(R3)+ ; Move length of EQUIVALENCE
MOVC3 (R1),@4(R1),(R3) ; Move EQUIVALENCE string
MOVL SET_STATE(AP),R1 ; Get address of SET_STATE
BEQLU 20$ ; If the address is 0, no SET_STATE
MOVB (R1),(R3)+ ; Move length of SET_STATE
MOVC3 (R1),@4(R1),(R3) ; Move SET_STATE string
.IF DEFINED VMS4_5
BISW2 #SYM_M_STATE, - ; Set STATE bit in FLAGS (just in case
SYM_W_FLAGS(R10) ; ... the caller did not set it)
.IF_FALSE
BISB2 #SYM_M_STATE, - ; Set STATE bit in FLAGS (just in case
SYM_B_FLAGS(R10) ; ... the caller did not set it)
.ENDC
20$: CLRB (R3)+ ; Clear last byte of template (could be
; ... taken as length of SET_STATE)
ADDL3 (SP)+,#SYM_T_SYMBOL+1,R0 ; Calculate the size of the template
ADDB2 SYM_T_SYMBOL(R10),R0 ; ...
ADDL2 #2,R0 ; Include word sum of the 3 lengths
ADDL2 #7,R0 ; Truncate to a quadword boundary
BICL2 #7,R0 ; Round to next quadword boundary
MOVW R0,SYM_W_SIZE(R10) ; Set the size of the queue entry
$CMEXEC_S - ; Go to executive mode to define key
ROUTIN=EXEC_KEYDEF ; ...
ADDL2 #WORK_BYTES,SP ; Clean up the stack
RET
;
; Executive mode routine to allocate CLI memory and insert the new key
; definition into the KEYPAD queue.
;
.ENTRY EXEC_KEYDEF,^M<R3,R11>
GET_PRC ; Get address or PRoCess data
MOVAB PRC_Q_ALLOCREG(R11),R3 ; Addr of free memory
MOVZWL SYM_W_SIZE(R10),R1 ; Get size of block to allocate
JSB @#EXE$ALLOCATE ; Allocate some CLI memory
MOVL #LIB$_INSCLIMEM,R0 ; Assume not enough CLI memory
TSTL R2 ; Was there space allocated?
BEQL 20$ ; No - return the error
PUSHR #^M<R0,R1,R2,R3,R4,R5> ; Copy the key definition from
MOVC5 SYM_W_SIZE(R10),(R10),#0, - ; ... the template on the
R1,(R2) ; ... stack to the memory just
POPR #^M<R0,R1,R2,R3,R4,R5> ; ... allocated
MOVW R1,SYM_W_SIZE(R10) ; Set actual length allocated
BSBW FIND_PLACE ; Find the queue position for
; ... the key definition
INSQUE SYM_L_FL(R2),@SYM_L_BL(R0) ; Insert key def into queue at
; ... position returned in R0
; ... (by FIND_PLACE)
MOVL #SS$_NORMAL,R0 ; Set successful return status
20$: RET ; Return to caller
;+
; For efficiency and special-processing, internal subroutines were used
; to find the queue position and delete an existing key definition (instead
; of using the CALLable routines HG$FIND_KEY and HG$DELETE_KEY).
;-
FIND_PLACE:
PUSHR #^M<R1,R2,R3,R4,R5,R6,R7,R8,R9> ; Save registers needed
MOVAB SYM_T_SYMBOL+1(R10),R1 ; Get addr of key name
MOVZBL -1(R1),R0 ; Get its length
ADDL2 R0,R1 ; R1 --> word length of rest
INCL R1 ; Bump R1 over the word length
INCL R1 ; R1 --> IF_STATE string
MOVZBL (R1)+,R0 ; Get the IF_STATE length
PUSHL R1 ; Put addr on the stack
PUSHL R0 ; Put the length on the stack
MOVZBL SYM_T_SYMBOL(R10),R8 ; R8 = length of key name
MOVAB SYM_T_SYMBOL+1(R10),R9 ; R9 --> key name
MOVL PRC_Q_KEYPAD(R11),R6 ; R6 --> first entry in queue
MOVAB PRC_Q_KEYPAD(R11),R7 ; R7 --> beginning of queue
10$: CMPL R6,R7 ; Reached end of queue?
BEQLU 20$ ; Yes - found place
; Check the state name
MOVAB SYM_T_SYMBOL+1(R6),R1 ; Get addr of key name
MOVZBL -1(R1),R0 ; Get its length
ADDL2 R0,R1 ; R1 --> word length of rest
INCL R1 ; Bump R1 over the word length
INCL R1 ; R1 --> IF_STATE string
MOVZBL (R1)+,R0 ; Get the IF_STATE length
CMPC5 (SP),@4(SP),#^A/ /,R0,(R1) ; Is this the same state?
BLSSU 20$ ; No - try next entry
BGTRU 15$ ; If >, no entries for IF_STATE
; Check the key names
MOVZBL SYM_T_SYMBOL(R6),R0 ; Get length of key name in que
CMPC5 R8,(R9),#^A/ /,R0,SYM_T_SYMBOL+1(R6) ; Compare the strings
BLSSU 20$ ; Found place if KEY < QUEUE KEY
BNEQU 15$ ; If not the same, go try next
;
; Here if key already exists
;
BSBW DELETEKEY ; Delete the key
BRB 20$ ; Return to caller
15$: MOVL SYM_L_FL(R6),R6 ; Get the next keypad entry
BRB 10$ ; ... and try again
20$: ADDL2 #8,SP ; Clean up stack
MOVL R6,R0 ; Return addr of QUEUE KEY entry
POPR #^M<R1,R2,R3,R4,R5,R6,R7,R8,R9> ; ...
RSB ; ...
DELETEKEY:
PUSHL R3 ; Save work register
MOVAB PRC_Q_ALLOCREG(R11),R3 ; Get allocation region listhead
REMQUE SYM_L_FL(R6),R0 ; Remove key def from the queue
MOVL SYM_L_FL(R0),R6 ; Make the Forward Link entry
; ... new "current" entry
MOVZWL SYM_W_SIZE(R0),R1 ; Get deleted entry size
JSB @#EXE$DEALLOCATE ; Deallocate the memory
MOVL (SP)+,R3 ; Restore register
RSB ; Return to caller
.END
;================ DELETE.MAR
.TITLE HG$DELETE_KEY
.IDENT "01-001"
;+
; Function: HG$DELETE_KEY
;
; Author: Hunter Goatley 15-MAY-1987
;
; Functional description:
;
; HG$DELETE_KEY performs the same function as the DCL DELETE/KEY command.
;
; Inputs:
;
; 4(AP) - Descriptor pointing to the keyname to delete
; 8(AP) - Descriptor pointing to /STATE string
;
; Outputs:
;
; Status in R0 - SS$_NORMAL, LIB$_NOTFOU
;
; Effects:
;
; Deletes DCL key definition
;
; Calling sequence:
;
; STATUS = HG$DELETE_KEY ("PF1","DEFAULT")
;-
.LIBRARY /DCL$KEYLIB:DCLKEY.MLB/
.LINK /SYS$SYSTEM:DCLDEF.STB/
.LINK /SYS$SYSTEM:SYS.STB/
.PSECT _HG$DELETE_KEY_CODE,EXE,NOWRT,PIC,SHR
KEY = 4
STATE = 8
.ENTRY HG$DELETE_KEY,^M<>
CHECK_ARGS #2 ; Check # of arguments
$CMEXEC_S - ; Need to be in EXEC mode
ROUTIN=EXEC_DELETE_KEY, - ; ...
ARGLST=(AP) ; ...
RET ; Return to caller
.ENTRY EXEC_DELETE_KEY,^M<R2,R3,R6>
GET_PRC ; Get the PRC address
MOVAL -(SP),R6 ; Get some stack space
PUSHAL (R6) ; Longword to receive address
PUSHL STATE(AP) ; Push state descriptor address
PUSHL KEY(AP) ; Push key descriptor address
CALLS #3,G^HG$FIND_KEY ; Find the key's queue address
BLBC R0,100$ ; Error? Exit with error
;
; Here if (R6) has valid keypad entry address
;
MOVL (R6),R6 ; Get address of entry
REMQUE SYM_L_FL(R6),R0 ; Remove the keypad entry from
; ... the keypad queue
MOVZWL SYM_W_SIZE(R0),R1 ; Get the size of the block
MOVAB PRC_Q_ALLOCREG(R11),R3 ; Get allocation region listhead
JSB @#EXE$DEALLOCATE ; Deallocate the block
MOVL #SS$_NORMAL,R0 ; Set successful return status
100$: TSTL (SP)+ ; Reset stack pointer
RET ; Return to caller
.END
;================ FIND.MAR
.TITLE HG$FIND_KEY
.IDENT "01-001"
;+
; Function: HG$FIND_KEY
;
; Author: Hunter Goatley 15-MAY-1987
;
; Functional description:
;
; HG$FIND_KEY returns the address of a key definition if it exists.
; If there is no matching definition, the address of the predecessor
; is returned.
;
; Environment:
;
; EXECutive mode
;
; Inputs:
;
; 4(AP) - Descriptor pointing to the keyname to define
; 8(AP) - Descriptor pointing to /IF_STATE= string
;
; Returns:
;
; R0 = SS$_NORMAL if key was found (Address in 12(AP))
; R0 = LIB$_NOTFOU if key was not found (Predecessor address in 12(AP))
;
; Effects:
;
; None.
;
; Calling sequence:
;
; STATUS = HG$FIND_KEY ("PF1","DEFAULT", ADDRESS%)
;-
.LIBRARY /DCL$KEYLIB:DCLKEY.MLB/
$SSDEF
$LIBDEF
.PSECT HG$FIND_KEY,EXE,NOWRT,SHR,PIC
KEY = 4
IF_STATE = 8
ADDR = 12
.ENTRY HG$FIND_KEY,^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>
CHECK_ARGS #3
GET_PRC ; Get the PRC address
MOVQ @KEY(AP),R8 ; Get key name descriptor
MOVQ @IF_STATE(AP),-(SP) ; Get IF_STATE descriptor
MOVL ADDR(AP),R10 ; Longword to return addr in
MOVL PRC_Q_KEYPAD(R11),R6 ; Get keypad queue listhead
MOVAB PRC_Q_KEYPAD(R11),R7 ; Get address of queue listhead
;
; Loop through all keypad entries until the correct entry is found.
;
10$: MOVL R6,(R10) ; Move PREDECESSOR address
CMPL R6,R7 ; Have we reached the end?
BEQLU 100$ ; ... (Current entry = listhead)
MOVAB SYM_T_SYMBOL(R6),R1 ; Addr of entry's KEY name
MOVZBL (R1)+,R0 ; Get length of KEY name
ADDL2 R0,R1 ; R1 --> word length of rest
TSTW (R1)+ ; Bump over the word length
MOVZBL (R1)+,R0 ; R0 = length of IF_STATE name
; R1 -> IF_STATE name
CMPC5 (SP),@4(SP),#^A/ /,R0,(R1) ; Is the state = given state?
BLSSU 100$ ; If <, no entries for given state
BGTRU 20$ ; If >, go check next keypad entry
;
; Here if we have a match on the IF_STATE
;
MOVZBL SYM_T_SYMBOL(R6),R0 ; R0 = len of entry's KEY name
CMPC5 R8,(R9),#^A/ /,R0,SYM_T_SYMBOL+1(R6) ; Right entry?
BLSSU 100$ ; If <, found predecessor
BNEQU 20$ ; If <>, try next entry
;
; Here if we found the target keypad entry
;
MOVL #SS$_NORMAL,R0 ; Set return status
BRB 110$ ; ... and go return to caller
20$: MOVL SYM_L_FL(R6),R6 ; Get addr of next keypad entry
BRB 10$ ; ... and check it out
100$: MOVL #LIB$_NOTFOU,R0 ; Return "Not found" status
110$: ADDL2 #8,SP ; Reset stack pointer
RET ; Return to caller
.END
;================ GETDEF.MAR
.TITLE HG$GET_KEYDEF
.IDENT "01-001"
;+
; Function: HG$GET_KEYDEF
;
; Author: Hunter Goatley 15-MAY-1987
;
; Functional description:
;
; HG$GET_KEYDEF returns the definition of a DCL key.
;
; Inputs:
;
; 4(AP) - Descriptor pointing to the keyname to return info about
; 8(AP) - Descriptor pointing to the state name
; 12(AP) - Descriptor pointing to buffer to receive equivalence string
; 16(AP) - Address of a word to receive the definition flags
; 20(AP) - Descriptor pointing to buffer to receive /SET_STATE string (0
; if the key does not SET_STATE)
;
; Outputs:
;
; Status in R0 - SS$_NORMAL, LIB$_NOTFOU
;
; Calling sequence:
;
; STATUS = HG$GET_KEYDEF ("PF1","DEFAULT",EQUIV$, FLAGS%, SETSTATE$)
;-
VMS4_5 = 1 ;**************************** Remove this line if under VMS 4.1
.LIBRARY /DCL$KEYLIB:DCLKEY.MLB/
.LINK /SYS$SYSTEM:SYS.STB/
.LINK /SYS$SYSTEM:DCLDEF.STB/
.PSECT _HG$GET_KEYDEF_CODE,EXE,NOWRT,PIC,SHR
KEY = 4
STATE = 8
EQUIV = 12
FLAGS = 16
SET_STATE = 20
.ENTRY HG$GET_KEYDEF,^M<>
CHECK_ARGS #5 ; Check # of arguments
$CMEXEC_S - ; Need to be in EXEC mode
ROUTIN=EXEC_GET_KEYDEF, - ; ...
ARGLST=(AP) ; ...
RET
.ENTRY EXEC_GET_KEYDEF,^M<R6>
GET_PRC ; Get the PRC address
MOVAL -(SP),R6 ; Get some stack space
PUSHAL (R6) ; Longword to receive address
PUSHL STATE(AP) ; Push state descriptor address
PUSHL KEY(AP) ; Push key descriptor address
CALLS #3,G^HG$FIND_KEY ; Find the key's queue address
BLBC R0,100$ ; Error? Exit with error
;
; Here if (R6) has valid keypad entry address
;
MOVL (R6),R6 ; Get address of entry
.IF DEFINED VMS4_5
MOVW SYM_W_FLAGS(R6),@FLAGS(AP) ; Copy flags to user's buffer
.IF_FALSE
MOVZBW SYM_B_FLAGS(R6),@FLAGS(AP) ; Copy flags to user's buffer
.ENDC
MOVAB SYM_T_SYMBOL(R6),R6 ; R6 -> symbol name
MOVZBL (R6)+,R0 ; Get length of key name
ADDL2 R0,R6 ; Bump R6 over key name
TSTW (R6)+ ; Bump R6 over word length
MOVZBL (R6)+,R0 ; R0 = length of IF_STATE name
ADDL2 R0,R6 ; R6 -> ASCIC equivalence string
MOVZWL (R6)+,R0 ; R0 = length of equiv. str.
; R6 -> equivalence string
PUSHL R0 ; ... onto the stack
MOVL SP,R0 ; ... and get their address
PUSHL R6 ; Push the length and address
PUSHAL (R0) ; ... buffer
PUSHL EQUIV(AP) ; ...
CALLS #3,G^STR$COPY_R ; ...
ADDL2 (SP)+,R6 ; R6 --> ASCIC SET_STATE string
MOVZBL (R6)+,-(SP) ; SP --> length
MOVL SP,R0 ; Get address of length
PUSHL R6 ; Push the length and address
PUSHAL (R0) ; ... buffer
PUSHL SET_STATE(AP) ; ...
CALLS #3,G^STR$COPY_R ; ...
100$: RET ; Return to caller
.END
;================ STATE.MAR
.TITLE HG$SET_KEYSTATE
.IDENT "01-001"
;+
; Function: HG$SET_KEYSTATE
;
; Author: Hunter Goatley 15-MAY-1987
;
; Functional description:
;
; HG$SET_KEYSTATE performs the same function as the DCL SET KEY/STATE=
; command.
;
; Inputs:
;
; 4(AP) - Descriptor pointing to the new key state name
; 8(AP) - Address of word to receive length of string returned
; (0 if not desired)
; 12(AP) - Descriptor pointing to buffer to receive old key state
; (0 if not desired)
;
; Outputs:
;
; Status in R0 - SS$_NORMAL, codes returned by STR$COPY_R
;
; Effects:
;
; Sets and returns DCL Key State
;
; Calling sequence:
;
; STATUS = HG$SET_KEYSTATE ("SETDEF", LENGTH%, OLDSTATE$)
;-
.LIBRARY /DCL$KEYLIB:DCLKEY.MLB/
.LINK /SYS$SYSTEM:SYS.STB/
.LINK /SYS$SYSTEM:DCLDEF.STB/
.PSECT _HG$SET_KEYSTATE_CODE,EXE,NOWRT,PIC,SHR
NEWSTATE = 4
OLDLEN = 8
OLDSTATE = 12
.ENTRY HG$SET_KEYSTATE,^M<>
CHECK_ARGS #3 ; Were enough arguments given?
10$: $CMEXEC_S - ; Need to be in EXEC mode
ROUTIN=EXEC_SET_KEYSTATE, - ; ...
ARGLST=(AP) ; ...
RET ; Return to caller
.ENTRY EXEC_SET_KEYSTATE,^M<R2,R3,R4,R5,R6,R7,R11>
GET_PRC ; Get the PRC adtress
MOVL PRC_L_CURRKEY(R11),R6 ; Get address of key state
TSTL OLDSTATE(AP) ; Did user want old state name?
BEQLU 10$ ; No - skip it
MOVZBL (R6),R0 ; Get the length of the state
PUSHL R0 ; Push the length
MOVL SP,R0 ; Get the address of the length
PUSHL R6 ; Push the string address
INCL (SP) ; Bump it past the length
PUSHAL (R0) ; Push address of length
PUSHL OLDSTATE(AP) ; Push return desc. address
CALLS #3,G^STR$COPY_R ; Copy the string to the buffer
POPL R1 ; Remove the old length
BLBC R0,100$ ; Error? Return if so
10$: TSTL OLDLEN(AP) ; Did user want return length?
BEQLU 20$ ; No - skip it
MOVZBW (R6),@OLDLEN(AP) ; Move the length
20$: TSTL NEWSTATE(AP) ; Did user give new state?
BEQLU 90$ ; No - skip it
; R6 --> current key state
MOVQ @NEWSTATE(AP),R4 ; Get new state descriptor
MOVZWL R4,R1 ; Move size to R1
INCL R1 ; Bump to include count byte
BSBB ALLOSTATE ; Get some new memory
MOVL R2,PRC_L_CURRKEY(R11) ; Set new state address
MOVB R4,(R2)+ ; Set the new state length
MOVC3 R4,(R5),(R2) ; Set the new keypad state
BSBB DEALSTATE ; Deallocate the old state
MOVL PRC_L_CURRKEY(R11), - ; Copy the key state address
PRC_L_LASTKEY(R11) ; ... to PRC_L_LASTKEY
90$: MOVL #SS$_NORMAL,R0 ; Set return status
100$: RET
DEALSTATE:
PUSHR #^M<R0,R1,R3> ; Save work registers
MOVAB PRC_Q_ALLOCREG(R11),R3 ; Get allocation region listhead
MOVL R6,R0 ; Get address
MOVZBL (R0),R1 ; Get deleted entry size
INCL R1 ; Bump to include length byte
ADDL2 #7,R1 ; Truncate to a quadword boundary
BICL2 #7,R1 ; Round to next quadword boundary
JSB @#EXE$DEALLOCATE ; Deallocate the memory
POPR #^M<R0,R1,R3> ; Restore work registers
RSB ; Return to caller
ALLOSTATE:
PUSHR #^M<R0,R1,R3> ; Save work registers
MOVAB PRC_Q_ALLOCREG(R11),R3 ; Get allocation region listhead
ADDL2 #7,R1 ; Truncate to a quadword boundary
BICL2 #7,R1 ; Round to next quadword boundary
JSB @#EXE$ALLOCATE ; Deallocate the memory
POPR #^M<R0,R1,R3> ; Restore work registers
RSB ; Return to caller
.END
LOGIN.MAR
.TITLE LOGIN.MAR Set up environment at LOGIN
;========================================================================
;= =
;= Programmer: Hunter Goatley =
;= Program: LOGIN.MAR =
;= System: VAX 11/785 VAX/VMS v4.2 =
;= Date: December 7, 1985 =
;= Purpose: Define logicals and foreign commands and other =
;= miscellaneous things =
;= =
;========================================================================
;
; MACRO EQUAL => Set up memory for each string and its
; equivalence string. .ASCIC is used.
.MACRO EQUAL SYMBOL,EQUIV
.ASCIC ?EQUIV?
.ASCIC ?SYMBOL?
.ENDM EQUAL
;
; KEY$KEYDEF => Set up memory for keypad definitions.
.MACRO KEY$KEYDEF KEY,EQUIV,IF_STATE=KDEFAULT,SET_STATE=0,FLAGS=FLAG1
.BYTE FLAGS ; Offset into FLAGS table
.BYTE SET_STATE ; Offset into STATE name table
.BYTE IF_STATE ; Offset into STATE name table
.ASCIC \EQUIV\ ; Equivalence string
.ASCIC \KEY\ ; Key name
.ENDM KEY$KEYDEF
.MACRO BUILD_DESCS
MOVZBW (R8)+,(R6) ; Move the string length to the desc.
MOVL R8,4(R6) ; Move the string address to the desc.
MOVZWL (R6),-(SP) ; Move the length to stack (word->long)
ADDL2 (SP)+,R8 ; Add to get addr of next string (count)
;
MOVZBW (R8)+,(R7) ; Move the string length to the desc.
MOVL R8,4(R7) ; Move the string address to the desc.
MOVZWL (R7),-(SP) ; Move the length to stack (word->long)
ADDL2 (SP)+,R8 ; Add to get addr of next string (count)
.ENDM BUILD_DESCS
.PSECT DATA,LONG,NOEXE,WRT
;
;====== The descriptor to be used for all equivalence strings.
EQUIV_DESC:
.WORD 0 ; Soon to be the length of the string
.BYTE DSC$K_DTYPE_T ; The type of string (character)
.BYTE DSC$K_CLASS_S ; The class (static)
.LONG 0 ; Soon to be the address
;===== The descriptor to be used for all logical names, symbol names, and
;===== keys to be defined.
SYM_LOG_DESC:
.WORD 0 ; Soon to be the length of the string
.BYTE DSC$K_DTYPE_T ; The type of string (character)
.BYTE DSC$K_CLASS_S ; The class (static)
.LONG 0 ; Soon to be the address
;
SYM_TABLE: .LONG LIB$K_CLI_GLOBAL_SYM ; Global symbol table-id
;
LOGICALS: ; The logicals to define
EQUAL <WORK>,<WKU$USER:[AC012116.WORK]>
EQUAL <DBG$INIT>,<WORK:DBGINI.DBG>
EQUAL <TPUSECINI>,<WORK:HUNTER.TPU$SECTION>
EQUAL <LNK$LIBRARY>,<SYS$LIBRARY:VAXCRTL>
.LONG 0 ; Terminate with 0
.ALIGN LONG
SYMBOLS: ; The symbols to set
EQUAL <AUDIT>,<$CDS$EXE:AUDIT.EXE>
EQUAL <CONTRL>,<$CDS$EXE:CONTRL>
EQUAL <KBLOCK>,<$CDS$EXE:KBLOCK>
EQUAL <T*YPE>,<TYPE>
EQUAL <WKUMON>,<$WORK:WKUMON.EXE>
.LONG 0 ; Terminate with a 0
.ALIGN LONG
KDEFAULT= 1
KCMD = 2
KSETDEF = 3
STATE_TABLE: ; Key state names vector table
.LONG 0 ; Null
.ADDRESS KDEFAULT_D ; state DEFAULT
.ADDRESS KCMD_D ; state CMD
.ADDRESS KSETDEF_D ; state SETDEF
KDEFAULT_D: .ASCID /DEFAULT/ ; ...
KCMD_D: .ASCID /CMD/ ; ...
KSETDEF_D: .ASCID /SETDEF/ ; ...
FLAG1 = 0 ; /ERASE/TERMINATE/ECHO
FLAG2 = 1 ; /ERASE/ECHO/SET_STATE
FLAG3 = 2 ; /ERASE/ECHO/NOTERMINATE
FLAG4 = 3 ; /NOERASE/ECHO/TERMINATE
FLAGS_TABLE:
.LONG SYM_M_ERASE!SYM_M_TERMINATE!SYM_M_ECHO
.LONG SYM_M_ERASE!SYM_M_ECHO!SYM_M_STATE
.LONG SYM_M_ERASE!SYM_M_ECHO
.LONG SYM_M_TERMINATE!SYM_M_ECHO
KEYS_TABLE:
KEY$KEYDEF <KP7>,<SHOW USERS>,,,FLAG1
KEY$KEYDEF <PF1>,<SET TERMINAL/NUMERIC>,,,FLAG1
KEY$KEYDEF <PF1>,<SET TERMINAL/NUMERIC>,KSETDEF,,FLAG1
KEY$KEYDEF <ENTER>,<SD >,,KSETDEF,FLAG2
KEY$KEYDEF <DO>,<CMD/>,,KCMD,FLAG2
;Key definitions for state DEFAULT ;
KEY$KEYDEF <E1>,<WKUmon>,,,FLAG1
KEY$KEYDEF <HELP>,<HELP >,,,FLAG3
;Key definitions for state SETDEF ;
KEY$KEYDEF <KP0>,<*>,KSETDEF,,FLAG4
KEY$KEYDEF <ENTER>,<SHOW DEFAULT>,KSETDEF,,FLAG1
; Key definitions for state CMD
KEY$KEYDEF <E1>,<LIST>,KCMD,,FLAG4
KEY$KEYDEF <E2>,<RESTORE>,KCMD,,FLAG4
KEY$KEYDEF <E3>,<FLUSH>,KCMD,,FLAG4
KEY$KEYDEF <E4>,<SAVE>,KCMD,,FLAG4
KEY$KEYDEF <E5>,<BOTH>,KCMD,,FLAG4
.LONG 0 ; Terminate with null longword
.ALIGN LONG
PROC_NAME: ; The process name to set
.ASCID /Goat Busters/
.PSECT LOGIN,EXE,NOWRT
.ENTRY LOGIN,^M<>
; Define all logicals for the process
MOVAB LOGICALS,R8 ; Get the beginning of the first symbol
MOVAQ EQUIV_DESC,R6 ; Get address of equivalence descriptor
MOVAQ SYM_LOG_DESC,R7 ; Get address of LOGICAL descriptor
10$: TSTB (R8) ; Are we finished (0 length)?
BEQLU DO_SYM ; Yes -- leave
BUILD_DESCS ; Build the string descriptors
PUSHL R6 ; Push addr of the equiv descriptor
PUSHL R7 ; Push addr of the logical descriptor
CALLS #2,G^LIB$SET_LOGICAL ; Go define it
BRB 10$ ; Loop until done
; Define all symbols for the process
DO_SYM:
MOVAB SYMBOLS,R8 ; Get the beginning of the first symbol
MOVAL SYM_TABLE,R9 ; Move the address of the table-id to R9
20$: TSTB (R8) ; Are we finished (0 length)?
BEQLU DO_KEYS ; Yes -- leave
BUILD_DESCS ; Set up the string descriptors
PUSHL R9 ; Push the address of table-id (global)
PUSHL R6 ; Push addr of the equiv descriptor
PUSHL R7 ; Push addr of the logical descriptor
CALLS #3,G^LIB$SET_SYMBOL ; Go define it
BRB 20$ ; Loop until done
; Define all keys for the process
DO_KEYS:
MOVAB KEYS_TABLE,R8 ; Get the beginning of the first symbol
MOVAQ EQUIV_DESC,R6 ; Get address of equivalence descriptor
MOVAQ SYM_LOG_DESC,R7 ; Get address of LOGICAL descriptor
30$: TSTL (R8) ; Are we finished (0 length)?
BEQLU DO_REST ; Yes -- leave
MOVZBL (R8)+,R0 ; Get offset in vector table
PUSHAL FLAGS_TABLE[R0] ; ...
MOVZBL (R8)+,R0 ; Get offset in vector table
PUSHL STATE_TABLE[R0] ; ...
MOVZBL (R8)+,R0 ; Get offset in vector table
PUSHL STATE_TABLE[R0] ; ...
BUILD_DESCS ; Build the string descriptors
PUSHL R6 ; Push addr of the equiv descriptor
PUSHL R7 ; Push addr of the logical descriptor
CALLS #5,HG$DEFINE_KEY ; Go define it
BRB 30$ ; Loop until finished
;
DO_REST: ;
$SETPRN_S - ; Set the process name
PRCNAM=PROC_NAME ; ...
$EXIT_S ; Return to VMS
ERR_BYE: ;
$EXIT_S R0 ; Return to VMS with error
.END LOGIN