;========================================================================
;=									=
;=	Programmer:	Hunter Goatley, Clyde Digital Systems, Orem, UT	=
;=	Program:	CMD.MAR						=
;=	Purpose:	Save, restore, flush, & list DCL command buffer	=
;=	Language:	VAX-11 MACRO32  assembly language		=
;=	System:		VAX/VMS v4.x					=
;=	Date:		October 8, 1986					=
;=									=
;========================================================================
;=									=
;=	Copyright (c) 1989 by Hunter Goatley				=
;=									=
;=	This program may be distributed freely for non-profit use	=
;=	as long as this notice remains intact.				=
;=									=
;========================================================================
;=									=
;=									=
;=	This  program  allows  the user to flush the command buffer,	=
;=	list all of the commands in  the buffer,  write the contents	=
;=	of the buffer to a file, and restore the buffer from a file.	=
;=									=
;=	To use this program, set up a foreign symbol, such as:		=
;=									=
;=			$ CMD :== $dev:[dir]CMD.EXE			=
;=			$ CMD/qualifier					=
;=									=
;=	To build:							=
;=			$ MACRO CMD					=
;=			$ LINK CMD/NOTRACEBACK				=
;=									=
;=	To install:							=
;=			$ INSTALL ADD CMD.EXE/PRIV=CMEXEC		=
;=									=
;=									=
;=	The valid qualifiers are:					=
;=									=
;=	/LIST	-  List all commands in the DCL command buffer		=
;=	/SAVE	-  Save the commands in SYS$LOGIN:SAVE_CMDS.TXT		=
;=	/RESTORE-  Restore commands previously SAVEd			=
;=	/FLUSH	-  Flush the command buffer				=
;=	/BOTH	-  SAVE, then FLUSH the DCL command buffer		=
;=									=
;========================================================================
;
	.LINK	"SYS$SYSTEM:SYS.STB"/selective_search
	.LINK	"SYS$SYSTEM:DCLDEF.STB"/selective_search

;
;  This macro compares the current address (in R10) with the beginning address
;  of the data area -- if they are equal, set the current address = to the end
;  of the data block (wrap).
;
	.MACRO	CHECK	?HERE
	CMPL	R11,R10			; Have we gone past the beginning?
	BNEQ	HERE			; No -- skip next instruction
	MOVAB	PRC_G_COMMANDS(R3),R10
	ADDL2	#PRC_S_COMMANDS+1,R10
;	MOVL	#CMD_BUF_END,R10	; Make R10 point to one byte past the
					; ... end of the data area.  With next
					; ... autodecrement, it will point to
					; ... the last byte of the table.
 HERE:	.ENDM	CHECK
					;
;
;  This macro compares the first four bytes of FOR_BUFF with a valid qualifier.
;  If there is a match, the appropriate routine is called and control branches
;  to return to VMS.
;
	.MACRO	PRESENT	QUAL,ROUTIN1,ROUTIN2=0,?LABEL
	CMPL	#^A\QUAL\,FOR_BUFF	; Was /QUAL given?
	BNEQU	LABEL			; No - try next
	CALLS	#0,ROUTIN1		; Call the proper routine
	.IF	DIF	0,ROUTIN2	; Was a second routine given?
	BLBC	R0,BYE			; Error?
	CALLS	#0,ROUTIN2		; No - call the second routine
	.ENDC				; ...
	BRW	BYE			; Return to VMS (only one qualifier
LABEL:	.ENDM	PRESENT			; ...  at a time!!!

	.PSECT	DCL_CMDS_DATA,NOEXE,WRT,LONG
					;
	$SSDEF				; Include status symbols
	$RMSDEF				; Include RMS symbols
	$FABDEF				; Include $FAB symbols
	$RABDEF				; Include $RAB symbols
	$PRVDEF				; Include privilege symbols
;
;=======================================================================
;
CMDFAB: $FAB	FNM=<SYS$LOGIN:SAVE_CMDS.TXT>, -	; File name
		FAC=<GET,PUT>, -		; File ACcess (R/W only)
		MRS=1420, -			; Maximum Record Size
		RAT=CR, -			; Record ATtributes
		ORG=SEQ				; File Organization (sequential)
;
;***  Record Access Block for SAVE_CMDS.TXT
;
CMDRAB:
	$RAB    FAB=CMDFAB, -		; Record Access Block
		RBF=CMDREC, -		; If writing, write from CMDREC
		UBF=CMDREC		; Address of SAVE_CMDS record buffer

;
;
CMDREC:	.BLKB	1032			; Buffer for DCL commands
;
;=======================================================================
;
MAXCMDLEN = 256				; Maximum length of a DCL command
					;
CMDBUFFER_D:				; Descriptor for the command buffer
	.WORD	MAXCMDLEN		; ...
	.BYTE	DSC$K_DTYPE_T		; ...
	.BYTE	DSC$K_CLASS_S		; ...
	.ADDRESS .+4			; ...
CMDBUFFER:				; The command buffer
	.BYTE	^A/ /[MAXCMDLEN]	; ...
					;
FAOCTR:	.ASCID	/!3UL !AS/
FAOUT:	.WORD	256				; ...
	.BYTE	DSC$K_DTYPE_T			; ...
	.BYTE	DSC$K_CLASS_S			; ...
	.ADDRESS .+4				; ...
	.BLKB	256

FOR_BUFF_D:				; Descriptor for the command line
	.WORD	MAXCMDLEN		; ...  returned by LIB$GET_FOREIGN
	.BYTE	DSC$K_DTYPE_T		; ...
	.BYTE	DSC$K_CLASS_S		; ...
	.ADDRESS .+4			; ...
FOR_BUFF:				; BUffer holding the command line
	.BLKB	MAXCMDLEN		; ...
FOR_LEN:.LONG	0			; Length of command line returned
USAGE_D:				; Usage message
	.ASCID	-
 \Must give valid qualifier: /LIST, /FLUSH, /SAVE, /RESTORE, /BOTH (save,flush)\
CMEXEC:	.QUAD	PRV$M_CMEXEC		; Privilege needed to flush & restore
CMKRNL:	.QUAD	PRV$M_CMKRNL		; Alternative privilege needed
;
;===============================================================================
;
	.PSECT	DCL_CMDS,EXE,NOWRT
	.ENTRY	DCL_CMDS,^M<>

	MOVW	#PRC_S_COMMANDS+4,-	; Store the size of the command buffer
		CMDRAB+RAB$W_RSZ	; ... in the RAB
	MOVW	#PRC_S_COMMANDS+4,-	; Store the size of the command buffer
		CMDRAB+RAB$W_USZ	; ... in the RAB

; A simple command line parser follows.  The code simply checks for a "/"
; followed by at least 3 characters of the command qualifier.  Not very
; forgiving, but easy to code.
;
	PUSHAW	FOR_LEN			; Get any commands on command line
	PUSHL	#0			; Don't want to prompt for anything
	PUSHAQ	FOR_BUFF_D		; Buffer for command line
	CALLS	#3,G^LIB$GET_FOREIGN	; Get the command line
	TSTW	FOR_LEN			; Was anything given?
	BNEQ	10$			; Yes  - see what was
	PUSHAQ	USAGE_D			; No - print usage message
	CALLS	#1,G^LIB$PUT_OUTPUT	; ...
	BRW	BYE			; ...
					;
10$:	PRESENT	</RES>,RESTORE_CMDS		; Was /RESTORE given?
	PRESENT	</LIS>,LIST_CMDS		; Was /LIST?
	PRESENT	</FLU>,FLUSH_CMDS		; Was /FLUSH?
	PRESENT	</SAV>,SAVE_CMDS		; Was /SAVE?
	PRESENT	</BOT>,SAVE_CMDS,FLUSH_CMDS	; Was /BOTH (save and flush)?

	PUSHAQ	USAGE_D			; If here, a valid qualifier was not
	CALLS	#1,G^LIB$PUT_OUTPUT	; ...  given - print USAGE message
 BYE:	$EXIT_S	CODE=R0			; Exit to VMS
					;
;===============================================================================
;   Subroutine SAVE_CMDS - Save the DCL commands in SYS$LOGIN:SAVE_CMDS.TXT
;
	.ENTRY	SAVE_CMDS,^M<>
						;
	$CREATE	FAB=CMDFAB			; Create the output file
	BLBC	R0,100$				; Error?
    	$CONNECT RAB=CMDRAB			; Connect the RAB
	BLBC	R0,100$				; Error?
	MOVAB	G^CTL$AG_CLIDATA,R3		; Get address of CLI data
	MOVL	PPD$L_PRC(R3),R3		; Get address of PRC region
	MOVC3	#PRC_S_COMMANDS+4, -		; Get the DCL previous command
		PRC_L_RECALLPTR(R3),CMDREC	; ... buffer (not protected
						; ... against user-mode reads)
	$PUT	RAB=CMDRAB			; Write the buffer to the file
	BLBC	R0,100$				; Error?
	$CLOSE	FAB=CMDFAB			; Close the file
 100$:	RET					; Return to caller
						;
;===============================================================================
;   Subroutine RESTORE_CMDS - Restore DCL commands in SYS$LOGIN:SAVE_CMDS.TXT
;   Calls EXEC_RESTORE from EXECUTIVE mode
;
	.ENTRY	RESTORE_CMDS,^M<>
						;
	CALLS	#0,GETPRV			; Turn on the CMEXEC to do this
	BLBC	R0,10$				; If error (No priv), return
	$OPEN	FAB=CMDFAB			; Open SAVE_CMDS.TXT for reading
	BLBC	R0,10$				; Error?
	$CONNECT RAB=CMDRAB			; Connect the RAB
	BLBC	R0,10$				; Error?
	$GET	RAB=CMDRAB			; Read the only record in the
						; ... file (the command buffer)
	BLBC	R0,10$				; Error?
	$CMEXEC_S -				; Go move the command buffer
		ROUTIN=EXEC_RESTORE		; ... to process space
	$CLOSE	FAB=CMDFAB			; Close the file
 10$:	RET					; Return to caller
;
;===============================================================================
;****	This executive mode routine moves the previous commands from CMDREC
;****	to the CLI data area.
;
	.ENTRY	EXEC_RESTORE,^M<>
		   				;
	MOVAL	HANDLER,(FP)			; Unnecessary handler, but...
	MOVAB	G^CTL$AG_CLIDATA,R3		; Get address of CLI data
	MOVL	PPD$L_PRC(R3),R3		; Get address of PRC region
	MOVC3	#PRC_S_COMMANDS+4,CMDREC, -	; Move the prev command block
		PRC_L_RECALLPTR(R3)		; ...  to the CLI data area
	MOVZBL	#SS$_NORMAL,R0			; Set up a success return
	RET					; Return to caller
						;
	.ENTRY	HANDLER,^M<>			; Exit handler in case of
	$EXIT_S					; ... $ACCVIO (Kill process)
						;
;===============================================================================
;   Subroutine LIST_CMDS - List all commands stored in DCL RECALL buffer
;
	.ENTRY	LIST_CMDS,^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>
	MOVAB	G^CTL$AG_CLIDATA,R3		; Get address of CLI data
	MOVL	PPD$L_PRC(R3),R3		; Get address of PRC region
	MOVL	PRC_L_RECALLPTR(R3),R10	; Get addr of end of most recent cmd
	MOVL	R10,R9			; Make a copy of it
	MOVAB	PRC_G_COMMANDS(R3),R11	; Get the beginning of the data block
	CLRL	R6			; Clear command counter
 10$:	MOVAB	CMDBUFFER,R8		; Get the buffer address
		   			; Clear the buffer
	MOVZBL	-(R10),R7		; Get the length of the first command
	BNEQ	20$
	BRW	40$			; 2 null bytes in a row? End
 20$:	MOVW	R7,CMDBUFFER_D		; Move the length to the descriptor
	CHECK				; Check to see if this command wraps
					; ...  to the end of the data area
	ADDL2	R7,R8			; Make R8 point to end of buffer
 30$:	MOVB	-(R10),-(R8)		; Move the first byte
	CHECK				; Check our addresses
	SOBGTR	R7,30$			; Loop until command is moved
	INCL	R6			; Bump the command counter
	$FAO_S	CTRSTR=FAOCTR, -	; Format for output
		OUTBUF=FAOUT, -		; ...
		OUTLEN=FAOUT, -		; ...
		P1=R6, -		; ...
		P2=#CMDBUFFER_D		; ...
	PUSHAQ	FAOUT			; Write it to the terminal
	CALLS	#1,G^LIB$PUT_OUTPUT	; ....
	MOVW	#256,FAOUT		; Reset FAO output descriptor
	DECL	R10			; Should now point to prefix count
	CHECK				; Check our addresses
	DECL	R10			; Should now point to 0 byte
	CHECK				; Check our addresses
	CMPL	R10,R9			; If current addr = starting addr
	BEQL	40$			; ...  end of data
	BRW	10$			; Loop until all commands printed
 40$:	MOVL	#SS$_NORMAL,R0		; Set return code
	RET				; Return to caller
					;
;===============================================================================
;   Subroutine FLUSH_CMDS - Flush the DCL command RECALL buffer
;
	.ENTRY	FLUSH_CMDS,^M<>
	CALLS	#0,GETPRV		; Turn on the CMEXEC to do this
	BLBC	R0,10$			; If error (No priv), return
	$CMEXEC_S -			; Flush the DCL command buffer
		ROUTIN=EXEC_FLUSH	; ...  (zero it out)
 10$:	RET				; Return to caller
;
;  Zero out the DCL command recall buffer
;
	.ENTRY	EXEC_FLUSH,^M<R2,R3,R4,R5>
	MOVAB	G^CTL$AG_CLIDATA,R3		; Get address of CLI data
	MOVL	PPD$L_PRC(R3),R3		; Get address of PRC region
	MOVAB	PRC_G_COMMANDS(R3),-		; Reset last command pointer
		PRC_L_RECALLPTR(R3)		; ...
	MOVC5	#0,#0,#0,#PRC_S_COMMANDS,-	; Zero out the DCL command
		PRC_G_COMMANDS(R3)		; ...  recall buffer
	MOVL	#SS$_NORMAL,R0			; Set return status
	RET					; Return to caller
						;
;===============================================================================
;   Subroutine GETPRV - Turn on either CMEXEC or CMKRNL to do FLUSH and RESTORE
;
	.ENTRY	GETPRV,^M<>
	$SETPRV_S -			; Turn on CMEXEC privilege
		ENBFLG=#1, -		; ...
		PRVADR=CMEXEC		; ...
	CMPL	#SS$_NORMAL,R0		; Was CMEXEC turned on?
	BEQLU	10$			; Yes - return
	$SETPRV_S -			; If not, try to turn on CMKRNL
		ENBFLG=#1, -		; ...
		PRVADR=CMKRNL		; ...
	CMPL	#SS$_NORMAL,R0		; Was CMEXEC turned on?
	BEQLU	10$			; Yes - return
	MOVL	#SS$_NOCMEXEC,R0	; If not, tell that CMEXEC is needed
 10$:	RET				; Return to caller

	.END	DCL_CMDS
