Copyright 1987-2016 by Kevin G. Barkes All rights reserved. This article may be duplicated or redistributed provided no alterations of any kind are made to this file. This edition of DCL Dialogue is sponsored by Networking Dynamics, developers and marketers of productivity software for OpenVMS systems. Contact our website www.networkingdynamics.com to download free demos of our software and see how you will save time, money and raise productivity! Be sure to mention DCL Dialogue! DCL DIALOGUE Originally published June, 1987 Stacks Revisited By Kevin G. Barkes As promised, this month we're featuring a series of command procedures which permit the "stacking" of privileges in DCL. Space permitting, next month we'll finish the set with a UIC stacker. These are permutations of the default directory utilities featured in the January issue, which elicited a great number of comments. For a complete discussion on the operation of DCL "stacks", please refer to the aforementioned January issue. PPUSH, PPOP, PSWAP and PRIVS are pretty much conceptually equivalent to their set default counterparts. PPUSH.COM is fully commented, and a study of that procedure should provide the reader with a fairly decent idea of the way in which privilege stacking is handled. There are a few other items which must be noted, however: - PPUSH will accept "DEFAULT" as a parameter; this will reset the user's privileges to those assigned to the global symbol LOGIN_PRIV, which is intended to be set in the user's LOGIN.COM file. PPUSH will create its own LOGIN_PRIV symbol for the DEFAULT parameter if it was not previously defined, using the current privilege state for the values. - Since setting privileges to NOALL results in the privilege-identifying lexical functions returning a null string, the procedures explicitly assign "NOALL" whenever this situation is encountered. - The string containing all possible privileges causes a DCL token overflow when attempts are made to write it to SYS$OUTPUT. Therfore, WRITE/SYMBOL is used in the procedures. The only exception to this is in PPUSH, where a normal WRITE statement is used to display the privileges enabled by the passed parameters, which are concatenated into P1. (By the way, the answer to last month's trivia question - how long is the string containing all possible privileges - is 261 bytes.) DCL hackers are encouraged to modify the procedures to format the display so that the privilege list doesn't extend off the right side of the screen or wrap unaesthetically. On the plus side, PPUSH et al permit the restoration of privileges to an identical, previously-set state. This is possible for two reasons; privileges are saved in the global symbols PRIV_(number), and a SET PROCESS/PRIVILEGE = NOALL is executed to "erase" all current privileges prior to resetting. This permits a greater degree of accuracy and control than by using the F$SETPRV() function, although procedures utilizing that lexical are generally smaller and will therefore execute faster. There is a piper to paid here, though. Even more so than the original stack procedures, PPUSH is a voracious user of DCL symbol space. This issue was raised by Jeff Templon, A. Smith, Craig Dickinson and others on ARIS and in a number of letters. When a privilege or default is "popped", the symbol space is not automatically freed. Depending on one's point of view, this can be either a bug or a feature. Assuming the procedures are used in a normal environment (that is, a concerted effort is not made to exceed the available symbol space), DCL should be able to handle the situation with no problem. Popped values decrement the stack pointer, so the previously assigned symbols are re-used. An occasional "pop" reduces the possibility of space problems. By not automatically deleting popped symbols, the user can manually reassign the stack pointer, recovering previously "lost" stack entries. For example, if you are five deep in the stack (the pointer symbol P_NUM is 5), accidentally pop to 4 and want to return to the previous settings, this can be accomplished by manually assigning the symbol P_NUM == 5 and doing a PSWAP. Popped entries can be deleted by simply adding to PPOP the line: $ DELETE/SYMBOL/GLOBAL PRIV_'P_NUM' prior to decrementing the stack. Use DIR_'D_NUM' if you insert the modification in original POP.COM file. Mr. Dickinson wrote a simple procedure which quickly deletes the "stack". It's similar to the DIRS.COM (and PRIVS.COM) procedure, except that the symbols are deleted rather than displayed. Mr. Smith noted that a simple SHOW/SYMBOL/GLOBAL DIR_* or PRIV_* will perform the same function as DIRS.COM and PRIVS.COM. This wildcarding was introduced in VMS 4.4. The original procedures, written by Don Liebes, were designed to operate under VMS 3.x, prior to the introduction of this capability. Also, the DIRS and PRIVS "commands" display the stacks in reverse numerical order. Still, it's a fast shortcut, and we thank both Messrs. Dickinson and Smith for their observations. ----- Ye Have Eyes But Cannot See Dept. - A quick perusal of the VT220 Pocket Programmer Guide failed to solve the problem posed by Philip J. Piotrowski ("Questions, Answers and Comments" - Feb. '87) regarding changing the hardware setting of a VT220 without having to go into the terminal's setup mode. On page 25, under Received Codes (Compatibility Level) is a list of the various escape sequences which will set the VT220's level of compatibility. By using the techniques outlined in the March column, it's a simple matter of assigning a WRITE SYS$OUTPUT (escape sequence) to one of the function keys, or better still, to execute a command procedure which does both a hardware setting and a "SET TERMINAL/DEVICE = " command. I don't know how I missed it the first time out. Thanks to Ray Howard, Glenn H. Myers, and others for their suggestions. Sorry I didn't repond directly, Glenn, but I didn't have your return address. And while on the subject of terminal manipulation, Pat Murphy dialed into ARIS with several pertinent observations about the March column. Like, for instance, why did I use [0,32] and therefore four bytes to assign the symbol containing the escape sequence? The answer's quite simple: I don't remember. I think it has something to do with a client site using non-standard terminals which required several nulls to follow the escape code. In any event, it was a habit I picked up a long time ago and never lost and, as you noted, seems to have no effect on the operation of the procedure. You can be certain I'll be more cautious when using bit-position and size modifiers in the future! In his ARIS discussion, Mr. Murphy reported that he uses a procedure similar to USETERM.COM, but one that uses the DCL lexical function F$GETDVI to get the terminal type. "That way," he explains, "you can differentiate between VT100/200/52 series and more, and can branch to a different set of symbol definitions for each." An excellent point, and one that I covered in a continuing series on lexicals in our sister publication, the VAX Professional. It should be noted that none of the DCL procedures which appear here are "definitive". They're intended to stimulate your creative juices, and beg for modification and customization to your site. Initially, I was a bit taken aback by the intensity of some of the reactions to the column. Now that I've learned to roll with the punches, it's a great experience. Every month I learn a new trick or two and, hopefully, you do as well. -------- ========================================================================== PROGRAM 1 ========================================================================== $! PPUSH.COM $! Save current privileges to the privilege "stack" $! and set requested new privileges. $! Disable error processing $ SET NOON $! If no privilege is specified, print help message $ IF P1 .NES. "" THEN GOTO CHECKPRIV $ WRITE SYS$OUTPUT "Usage: PPUSH " $ EXIT $ CHECKPRIV: $! If LOGIN_PRIV was not defined in the user's login.com file, $! do it now: $ IF F$TYPE(LOGIN_PRIV) .EQS. "" THEN LOGIN_PRIV == F$GETJPI("","CURPRIV") $! Save current privileges $ ORIGINAL_PRIV = F$GETJPI("","CURPRIV") $ IF ORIGINAL_PRIV .EQS. "" THEN ORIGINAL_PRIV := NOALL $! In case the user put spaces between the commas and privileges, $! concatenate the parameters: $ P1 = P1+P2+P3+P4+P5+P6+P7+P8 $! If resetting with "DEFAULT", set privileges accordingly: $ IF P1 .NES. "DEFAULT" THEN GOTO SET_NEWPRIV $ P1 = LOGIN_PRIV $! "Clear" current privileges $ SET PROCESS/PRIV = NOALL $ WRITE SYS$OUTPUT "Resetting default privs:" $ SET_NEWPRIV: $! Try to set the requested privileges: $ SET PROCESS/PRIVILEGE = ('P1') $! If the command fails, exit with a message: $ IF .NOT. $STATUS THEN GOTO NO_PRIVS $! Initialize the "stack" if procedure hasn't been used before: $ IF F$TYPE(P_NUM) .EQS. "" THEN P_NUM == 0 $! Increment the stack: $ P_NUM == P_NUM + 1 $! "Push" the old privileges on the stack $ PRIV_'P_NUM' :== 'ORIGINAL_PRIV' $ WRITE SYS$OUTPUT "Privileges enabled: ''P1'" $ EXIT $! If the new set privs failed, restore the original privs. $ NO_PRIVS: $! "Clear" all privs: $ SET PROCESS/PRIV = NOALL $! Restore original privs: $ SET PROCESS/PRIV = ('ORIGINAL_PRIV') $ WRITE SYS$OUTPUT "Privileges not assigned." $ EXIT ============================================================================= PROGRAM 2 ============================================================================= $! PPOP.COM $! "POP"s last privilege from privilege stack. $! Disable error processing $ SET NOON $! Save current privs: $ ORIGINAL_PRIV = F$GETJPI("","CURPRIV") $! Exit if nothing to pop; otherwise, pop $ IF F$TYPE(PRIV_'P_NUM') .EQS. "" THEN GOTO NO_POP $ IF F$INTEGER(P_NUM) .GT. 0 THEN GOTO DO_POP $ NO_POP: $ WRITE SYS$OUTPUT "No PUSH to POP! Stack pointer: ",P_NUM $ EXIT $! "Pop" the last default from the "stack" $ DO_POP: $! "Clear" all privileges $ SET PROCESS/PRIV = NOALL $ NEWPRIVS = PRIV_'P_NUM' $ SET PROCESS/PRIV = ('NEWPRIVS') $ IF .NOT $STATUS THEN GOTO DO_ERROR $! Decrement "stack" $ P_NUM == P_NUM - 1 $ CPRIVS = F$GETJPI("","CURPRIV") $ WRITE SYS$OUTPUT "Current privs: " $ WRITE/SYMBOL SYS$OUTPUT CPRIVS $ EXIT $! Handle errors: $ DO_ERROR: $ WRITE SYS$OUTPUT "Invalid privs ",PRIV_'P_NUM' $ SET PROCESS/PRIV = NOALL $ SET PROCESS/PRIV = ('ORIGINAL_PRIV') $ CPRIVS = F$GETJPI("","CURPRIV") $ WRITE SYS$OUTPUT "Privs remain:" $ WRITE/SYMBOL SYS$OUTPUT CPRIVS $ P_NUM == P_NUM - 1 $ IF F$INTEGER(P_NUM) .EQ. 0 THEN EXIT $ WRITE/SYMBOL SYS$OUTPUT "Next item on stack: ",P_NUM,". ",PRIV_'P_NUM' $ EXIT ============================================================================= PROGRAM 3 ============================================================================= $! PSWAP.COM $! "Swap" privileges previously stored on privilege stack $! Disable error processing $ SET NOON $! Exit if nothing to "swap" $ IF F$TYPE(P_NUM) .EQS. "" THEN GOTO NO_SWAP $ IF F$TYPE(PRIV_'P_NUM') .EQS. "" THEN GOTO NO_SWAP $ IF F$INTEGER(P_NUM) .GT. 0 THEN GOTO DO_SWAP $ NO_SWAP: $ WRITE SYS$OUTPUT "Nothing to SWAP! Stack pointer: ",P_NUM $ EXIT $ DO_SWAP: $ IF P1 .EQS. "" THEN P1 = P_NUM $ IF P1 .GT. 0 .AND. P1 .LE. P_NUM THEN GOTO DO_SWITCH $ WRITE SYS$OUTPUT "SWAP out of range!" $ EXIT $ DO_SWITCH: $ ORIGINAL_PRIV = F$GETJPI("","CURPRIV") $ IF ORIGINAL_PRIV .EQS. "" THEN ORIGINAL_PRIV := NOALL $ SET PROCESS/PRIV = NOALL $ NEWPRIVS = PRIV_'P1' $ SET PROCESS/PRIVS = ('NEWPRIVS') $ IF .NOT. $STATUS THEN GOTO DO_ERROR $ PRIV_'P1' == ORIGINAL_PRIV $ WRITE SYS$OUTPUT "Privs changed to:" $ CPRIVS = F$GETJPI("","CURPRIV") $ IF CPRIVS .EQS. "" THEN CPRIVS := NOALL $ WRITE/SYMBOL SYS$OUTPUT CPRIVS $ EXIT $! Handle errors: $ DO_ERROR: $ WRITE SYS$OUTPUT "Invalid privs: " $ WRITE/SYMBOL SYS$OUTPUT PRIV_'P1' $ SET PROCESS/PRIV = NOALL $ SET PROCESS/PRIV = 'ORIGINAL_PRIV' $ WRITE SYS$OUTPUT "Privs remain:" $ CPRIVS = F$GETJPI("","CURPRIV") $ IF CPRIVS .EQS. "" THEN CPRIVS := NOALL $ WRITE/SYMBOL SYS$OUTPUT CPRIVS $ EXIT ============================================================================== PROGRAM 4 ============================================================================== $! PRIVS.COM $! Displays contents of the PRIVS stacks $! Disable error processing $ SET NOON $! Exit if nothing to display $ IF F$TYPE(P_NUM) .EQS. "" .OR. - F$TYPE(P_NUM) .EQS. "STRING" THEN GOTO NO_PRIV $ IF F$INTEGER(P_NUM) .GT. 0 THEN GOTO DO_PRIV $ NO_PRIV: $ WRITE SYS$OUTPUT "Nothing in stack." $ CPRIV = F$GETJPI("","CURPRIV") $ IF CPRIV .EQS. "" THEN CPRIV := NOALL $ WRITE SYS$OUTPUT "Current privs:" $ WRITE/SYMBOL SYS$OUTPUT CPRIV $ EXIT $! Display "stack" $ DO_PRIV: $ CPRIV = F$GETJPI("","CURPRIV") $ IF CPRIV .EQS. "" THEN CPRIV := NOALL $ WRITE SYS$OUTPUT "Current privs:" $ WRITE/SYMBOL SYS$OUTPUT CPRIV $ L_NUM = P_NUM $ DO_LOOP: $ IF F$TYPE(PRIV_'L_NUM') .EQS. "" THEN GOTO DEC_COUNTER $ WRITE/SYMBOL SYS$OUTPUT "(''L_NUM'.) ",PRIV_'L_NUM' $! Decrement counter $ DEC_COUNTER: $ L_NUM = L_NUM - 1 $ IF L_NUM .EQ. 0 THEN EXIT $ GOTO DO_LOOP $ EXIT ---------- Kevin G. Barkes is an independent consultant. He publishes the KGB Report newsletter, operates the www.kgbreport.com website, lurks on comp.os.vms, and can be reached at kgbarkes@gmail.com.