Learn how to use the Command Analyzer Change exit point.
By Bruce Vining
In the last column, we saw how proxy commands can be used to provide aliases for commands such as ENDJOB and SIGNOFF. Today, we'll look at how the Command Analyzer Change exit point can be used to conditionally alter the command string that is actually processed when using the KILL proxy command.
The Command Analyzer Change exit point is documented here. The exit point passes to the exit program three parameters:
Required Parameter Group
1 |
Change command exit information |
Input |
Char(*) |
2 |
Replacement command |
Output |
Char(*) |
3 |
Length of replacement command string |
Output |
Binary(4) |
The first parameter, Change command exit information, is a structure providing information about the command being processed. This structure is defined in the exit point documentation this way:
Format CHGC0100
Offset |
Type |
Field |
|
Dec |
Hex |
||
0 |
0 |
CHAR(20) |
Exit point name |
20 |
14 |
CHAR(8) |
Exit point format name |
28 |
1C |
CHAR(10) |
Command name |
38 |
26 |
CHAR(10) |
Library name |
48 |
30 |
CHAR(1) |
Change allowed indicator |
49 |
31 |
CHAR(1) |
Prompt indicator |
50 |
32 |
CHAR(2) |
Reserved |
52 |
34 |
BINARY(4) |
Offset to command string |
56 |
38 |
BINARY(4) |
Length of command string |
60 |
3C |
BINARY(4) |
Offset to proxy chain |
64 |
41 |
BINARY(4) |
Number of entries in proxy chain |
|
|
CHAR(*) |
Command string |
Proxy commands and libraries. These fields repeat in the order listed. |
CHAR(10) |
Proxy command name |
|
CHAR(10) |
Proxy command library name |
The first two fields, Exit point name and Exit point format name, are passed to the exit program so that the program can know the format of the information being passed. The Exit point name, QIBM_QCA_CHG_COMMAND, indicates that the exit program is being called by the Command Analyzer Change exit point. The Exit point format name, CHGC0100, indicates that the structure being passed is defined as shown above. Currently, the Command Analyzer Change exit point supports only one format, so this field may not appear to be necessary. But in the future, other formats could be added to provide additional (or at least different) information. On your system, you may have a combination of user exit programs, some using the initial format and some using the new format. As you will see later, you tell the system which format you want used when the exit program is called. This format flexibility is done so that user exit programs written to this format do not have to be changed as new formats are added. If the system is accidently told to call with a new format CHGCxxxx, and the exit program is written only expecting to use format CHGC0100, then the exit program can use the Exit point format name field to verify that it has been called correctly--prior to processing the remaining fields in the structure.
The next two fields, Command name and Library name, identify what command is being processed. In the case of proxy commands, this command name is the actual target command to be run. So if we used the proxy command KILL, which has a target command of ENDJOB, we would find ENDJOB as the Command name.
Following these fields, the Change allowed indicator field informs the exit program if it can actually change the command to be run. In certain cases, the exit program cannot modify the command string (these are documented in the exit point documentation). If this field is '1', indicating that the exit program can modify the command to be run, then the program can add parameters, remove parameters, and even change the command to be run. The exit program does this by returning the "new" command string in the second parameter passed to the exit program, Replacement command, and returning the length of the "new" command string in the third parameter, Length of replacement command string.
The next field, Prompt indicator, tells the exit program if prompting has been requested for the command. Note that the exit program is called by the system prior to any prompting.
Later in this structure are the Offset to command string and Length of command string fields. Since the system cannot know in advance what parameters the user might specify at run time, the command string is not defined with a fixed location or length. These fields, which are at a fixed location, tell the exit program where to find the start of the command string and how long the command string is.
Similar to how the system does not know in advance how long the command string will be, the system also does not know how many proxy commands might be linked together in order to access the actual target command. In our case, we have the proxy command KILL with the target command ENDJOB. But you could also have the proxy command DIE with the target command of KILL, where KILL is another proxy command with the target command of ENDJOB. This chain of proxy commands used to finally find ENDJOB is passed to the exit program. Due to the variable-length nature of the proxy chain, the proxy chain is accessed by the fields Offset to proxy chain and Number of entries in proxy chain. Each entry in the proxy chain is 20 bytes in length--the proxy command name and the library where the proxy command was located.
To define this structure in CL, we have a few choices. We could define the first parameter, Change command exit information, as a large *CHAR field and then use built-ins such as %SST and %BIN to extract the information we need. Historically, this is how CL programs generally supported a structure being passed as a parameter. A major drawback to this approach is that this structure is, as we saw earlier, variable length. With the limited number of parameters supported with the ENDJOB command, we could safely (at least for this release) guess a maximum length of 1000 bytes and be confident that this would be sufficient for our purposes. But another command, such as Start Journal (STRJRN), could easily have a command string much longer, and who knows what ENDJOB will even look like in 10 years. By using this approach, we need to guess and specify a maximum length as built-ins such as %SST will check to make sure the program is not trying to access data beyond the declared length of the variable.
A much better approach would be to use the V5R4 CL enhancements related to pointer support, based storage support, and defined storage support. Using these capabilities, we can eliminate guesswork as to the size of the Change command exit information parameter and at the same time improve performance. One possible definition of the Change command exit information parameter, utilizing these V5R4 features, is provided here:
Dcl Var(&Chg_Info) Type(*Char) Len(68)
Dcl Var(&EP_Name) Type(*Char) Stg(*Defined) +
Len(20) DefVar(&Chg_Info 1)
Dcl Var(&EP_Format) Type(*Char) Stg(*Defined) +
Len(8) DefVar(&Chg_Info 21)
Dcl Var(&Cmd_Name) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Chg_Info 29)
Dcl Var(&Cmd_Lib) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Chg_Info 39)
Dcl Var(&Alw_Chg) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Chg_Info 49)
Dcl Var(&Prompt) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Chg_Info 50)
Dcl Var(&Off_InlCmd) Type(*Int) Stg(*Defined) +
DefVar(&CHG_INFO 53)
Dcl Var(&Len_InlCmd) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 57)
Dcl Var(&Off_Prx) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 61)
Dcl Var(&Nbr_Prx) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 65)
Dcl Var(&Ptr_InlCmd) Type(*Ptr)
Dcl Var(&Inl_Cmd) Type(*Char) Stg(*Based) +
Len(32767) BasPtr(&Ptr_InlCmd)
Dcl Var(&Ptr_Prx) Type(*Ptr)
Dcl Var(&Prx_Struct) Type(*Char) Stg(*Based) +
Len(20) BasPtr(&Ptr_Prx)
Dcl Var(&Prx_Name) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Prx_Struct 1)
Dcl Var(&Prx_Lib) TYPE(*Char) Stg(*Defined) +
Len(10) DefVar(&Prx_Struct 11)
The structure, as passed by the exit point, is actually much larger than the declared 68-byte length declared above, but we only need to define the fixed portion of the structure. The remaining information--the command to be run and the proxy commands used to reach the command to be run--will be accessed later by way of pointers (&Ptr_InlCmd and &Ptr_Prx, respectively) and based variables (&Inl_Cmd and &Prx_Struct, respectively). Pointer offsets are not checked at run time in the same way substring operations are checked (though the system still performs some checks that we will not get into at this time). The use of *defined fields also means that you do not have to physically copy data from the structure to individual fields with operations such as the %SST built-in. You can simply reference the data as it exists within the structure. This elimination of copying the data provides a small performance improvement.
The other two parameters passed to the exit program are simple character variables and integer variables. As such they can be defined this way:
Dcl Var(&New_Cmd) Type(*Char) Len(32000)
Dcl Var(&Len_NewCmd) Type(*Int)
Using the exit point documentation and the definitions shown above, the following user exit program, CHKKILL (Check for KILL), will conditionally add OPTION(*IMMED) to an ENDJOB command if the command was run due to the proxy command KILL.
This source should be stored in a source member named CHKKILL. If you encounter an error associated with the Retrieve Variable Size (RTVVARSIZ) command not being found, you need to refer to the earlier article "Just How Big Is That Variable?" The source for this command and the command processing program can be found there.
Pgm Parm(&Chg_Info &New_Cmd &Len_NewCmd)
Dcl Var(&Chg_Info) Type(*Char) Len(68)
Dcl Var(&EP_Name) Type(*Char) Stg(*Defined) +
Len(20) DefVar(&Chg_Info 1)
Dcl Var(&EP_Format) Type(*Char) Stg(*Defined) +
Len(8) DefVar(&Chg_Info 21)
Dcl Var(&Cmd_Name) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Chg_Info 29)
Dcl Var(&Cmd_Lib) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Chg_Info 39)
Dcl Var(&Alw_Chg) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Chg_Info 49)
Dcl Var(&Prompt) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Chg_Info 50)
Dcl Var(&Off_InlCmd) Type(*Int) Stg(*Defined) +
DefVar(&CHG_INFO 53)
Dcl Var(&Len_InlCmd) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 57)
Dcl Var(&Off_Prx) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 61)
Dcl Var(&Nbr_Prx) TYPE(*Int) Stg(*Defined) +
DefVar(&Chg_Info 65)
Dcl Var(&New_Cmd) Type(*Char) Len(32000)
Dcl Var(&Len_NewCmd) Type(*Int)
Dcl Var(&Ptr_InlCmd) Type(*Ptr)
Dcl Var(&Inl_Cmd) Type(*Char) Stg(*Based) +
Len(32767) BasPtr(&Ptr_InlCmd)
Dcl Var(&Ptr_Prx) Type(*Ptr)
Dcl Var(&Prx_Struct) Type(*Char) Stg(*Based) +
Len(20) BasPtr(&Ptr_Prx)
Dcl Var(&Prx_Name) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Prx_Struct 1)
Dcl Var(&Prx_Lib) TYPE(*Char) Stg(*Defined) +
Len(10) DefVar(&Prx_Struct 11)
Dcl Var(&Limit) Type(*Int)
Dcl Var(&Size_PrxID) Type(*Int)
Dcl Var(&Size_MatPG) Type(*Int)
Dcl Var(&Pkd_InlCmd) Type(*Dec) Len(3 0)
Dcl Var(&StrPos) Type(*Dec) Len(3 0) +
Value(1)
Dcl Var(&Len_Option) Type(*Dec) Len(3 0) +
Value(7)
Dcl Var(&Result) Type(*Dec) Len(3 0)
Dcl Var(&MsgID) Type(*Char) Len(7)
Dcl Var(&ErrMsgDta) Type(*Char) Len(40)
Dcl Var(&PgmName) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ErrMsgDta 1)
Dcl Var(&LibName) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ErrMsgDta 11)
Dcl Var(&ErrEP_Name) Type(*Char) Stg(*Defined) +
Len(20) DefVar(&ErrMsgDta 21)
Dcl Var(&ErrEP_Fmt) Type(*Char) Stg(*Defined) +
Len(8) DefVar(&ErrMsgDta 21)
Dcl Var(&Err_CmdNam) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ErrMsgDta 21)
Dcl Var(&Err_InlLen) Type(*Int) Stg(*Defined) +
DefVar(&ErrMsgDta 21)
Dcl Var(&Err_Scan) Type(*Dec) Stg(*Defined) +
Len(3 0) DefVar(&ErrMsgDta 21)
Dcl Var(&Pgm_Struct) Type(*Char) Len(80)
Dcl Var(&BytPrv) Type(*Int) Stg(*Defined) +
DefVar(&Pgm_Struct 1)
Dcl Var(&BytAvl) Type(*Int) Stg(*Defined) +
DefVar(&Pgm_Struct 5)
Dcl Var(&PgmFmt) Type(*Int) Stg(*Defined) +
DefVar(&Pgm_Struct 9)
Dcl Var(&Lib) Type(*Char) Stg(*Defined) +
Len(30) DefVar(&Pgm_Struct 19)
Dcl Var(&Pgm) Type(*Char) Stg(*Defined) +
Len(30) DefVar(&Pgm_Struct 51)
MonMsg MsgID(CPF0000 MCH0000) +
Exec(GoTo CmdLbl(CatchAll))
If Cond(&EP_Name *NE 'QIBM_QCA_CHG_COMMAND') +
Then(Do)
Chgvar Var(&MsgID) Value('PRX1001')
ChgVar Var(&ErrEP_Name) Value(&EP_Name)
CallSubr Subr(SndErrMsg)
EndDo
If Cond(&EP_Format *NE 'CHGC0100') Then(Do)
Chgvar Var(&MsgID) Value('PRX1002')
ChgVar Var(&ErrEP_Fmt) Value(&EP_Format)
CallSubr Subr(SndErrMsg)
EndDo
If Cond(&Cmd_Name *NE 'ENDJOB') Then(Do)
Chgvar Var(&MsgID) Value('PRX1003')
ChgVar Var(&Err_CmdNam) Value(&Cmd_Name)
CallSubr Subr(SndErrMsg)
EndDo
If Cond(&Nbr_Prx *EQ 0) Then(Return)
ChgVar Var(&Ptr_Prx) Value(%Addr(&Chg_Info))
ChgVar Var(%OffSet(&Ptr_Prx)) +
Value(%OffSet(&Ptr_Prx) + &Off_Prx)
RtvVarSiz Var(&Prx_Struct) Size(&Size_PrxID)
DoFor Var(&Limit) From(1) To(&Nbr_Prx)
If Cond(&Prx_Name = 'KILL') Then(Do)
If Cond(&Len_InlCmd *GT 999) Then(Do)
ChgVar Var(&MsgID) Value('PRX1004')
ChgVar Var(&Err_InlLen) +
Value(&Len_InlCmd)
CallSubr Subr(SndErrMsg)
EndDo
ChgVar Var(&Ptr_InlCmd) +
Value(%Addr(&Chg_Info))
ChgVar Var(%OffSet(&Ptr_InlCmd)) +
Value(%OffSet(&Ptr_InlCmd) + &Off_InlCmd)
ChgVar Var(&Pkd_InlCmd) Value(&Len_InlCmd)
Call Pgm(QCLScan) Parm(&Inl_Cmd +
&Pkd_InlCmd &StrPos 'OPTION(' +
&Len_Option '0' '0' ' ' &Result)
Select
When Cond(&Result *GT 0) Then(Return)
When Cond((&Result *EQ 0) *OR +
(&Result *EQ -1)) Then(Do)
If Cond(&Alw_Chg *EQ '0') Then(Do)
ChgVar Var(&MsgID) Value('PRX1005')
CallSubr Subr(SndErrMsg)
EndDo
Else Cmd(Do)
ChgVar Var(&New_Cmd) +
Value(%SST(&Inl_Cmd 1 +
&Len_InlCmd) *BCat +
'OPTION(*IMMED)')
ChgVar Var(&Len_NewCmd) +
Value(&Len_InlCmd + 15)
EndDo
Return
EndDo
When Cond(&Result *LT 0) Then(Do)
ChgVar Var(&MsgID) Value('PRX1006')
ChgVar Var(&Err_Scan) Value(&Result)
CallSubr Subr(SndErrMsg)
EndDo
EndSelect
EndDo
ChgVar Var(%OffSet(&Ptr_Prx)) +
Value(%OffSet(&Ptr_Prx) + &Size_PrxID)
EndDo
Return
CatchAll: ChgVar Var(&MsgID) Value('PRX1099')
CallSubr Subr(SndErrMsg)
Subr Subr(SndErrMsg)
RtvVarSiz Var(&Pgm_Struct) Size(&Size_MatPG)
CallPrc Prc('_PROPB') Parm((&Pgm_Struct) +
(x'00' *ByVal) (&Size_MatPG *ByVal))
ChgVar Var(&BytPrv) Value(&Size_MatPG)
CallPrc Prc('_MATPGMNM') Parm((&Pgm_Struct))
ChgVar Var(&PgmName) Value(%SST(&Pgm 1 10))
ChgVar Var(&LibName) Value(%SSt(&Lib 1 10))
SndPgmMsg MsgID(&MsgID) MsgF(VINING/USERMSGF) +
MsgDta(&ErrMsgDta) MsgType(*Escape)
EndSubr
EndPgm
To compile the CHKKILL program, you could use this command:
CRTBNDCL PGM(CHKKILL) TEXT('Check for KILL Proxy')
If you look at the CHKKILL program, you will see that it sends various escape messages for errors that are encountered. To create these messages, use the following commands:
CRTMSGF MSGF(VINING/USERMSGF)
ADDMSGD MSGID(PRX1001) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 was incorrectly called by exit point &3.')
FMT((*CHAR 10) (*CHAR 10) (*CHAR 20))
ADDMSGD MSGID(PRX1002) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 was incorrectly called with format &3.')
FMT((*CHAR 10) (*CHAR 10) (*CHAR 8))
ADDMSGD MSGID(PRX1003) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 was incorrectly called for command &3.')
FMT((*CHAR 10) (*CHAR 10) (*CHAR 10))
ADDMSGD MSGID(PRX1005) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 was not able to change OPTION default.')
FMT((*CHAR 10) (*CHAR 10))
ADDMSGD MSGID(PRX1006) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 encountered return code &3 on scan.')
FMT((*CHAR 10) (*CHAR 10) (*DEC 3 0))
ADDMSGD MSGID(PRX1099) MSGF(VINING/USERMSGF)
MSG('Exit program &2/&1 ended unexpectedly. See previous messages.')
FMT((*CHAR 10) (*CHAR 10))
We have run out of room for this month's article, but next month we will look at the initial flow of the CHKKILL program. We will also see how to activate CHKKILL so that it is called by the system whenever the ENDJOB command is run (either directly as ENDJOB or indirectly through a proxy such as KILL). In the meantime, feel free to review the CHKKILL source to determine how it works. Hopefully, you will learn, or at least confirm, a few approaches on how to get the most out of CL!
Program Puzzler
Using the above program, CHKKILL will add the literal 'OPTION(*IMMED)' when processing a KILL proxy command that has not already specified the OPTION keyword. Related to this behavior, I have intentionally included one statement in the program that I consider to be a poor programming practice:
ChgVar Var(&Len_NewCmd) +
Value(&Len_InlCmd + 15)
The problem I have with this statement is that I have to count the number of characters being concatenated to the initial command string, &Inl_Cmd, in order to set the length of the new command string, &Len_NewCmd. In this case the hardcoded "15" represents the 14 characters found in the literal value 'OPTION(*IMMED)' plus one character for the blank separating this literal value from the initial command string. Can you determine a way to eliminate the need to count the 15 characters? Your answer should also provide for a developer in the future adding additional keywords such as 'LOGLMT(0)' to the above 'OPTION(*IMMED)' literal without the developer needing to change how the &Len_NewCmd variable is set (in this case changing the "15" to "25"). The one hint I will give is that the answer is in the article "Just How Big Is That Variable?" The answer I'm thinking of can be found here under The CL Corner: Program Puzzler, though other solutions do exist.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online