Register and validate the Command Analyzer Change exit point.
This article is the third in a series that discusses how to use proxy commands and the Command Analyzer Change exit point. Before reading this article, you may find it beneficial to review the two prior articles:
In the last article, you saw the source for the Check for KILL exit program CHKKILL, but we had not had the opportunity to examine the program in detail. We'll start doing so now. The approach we'll take is to step through the processing flow of the program, explaining any new data definitions as we encounter them. For space reasons, the source for program CHKKILL is not being repeated in this article, so you may want to refer to the previous article.
To best understand some of the initial checks being done by the CHKKILL program, let's see how you tell the operating system that you want the CHKKILL program to be called whenever the ENDJOB command might be run.
To register CHKKILL with the Command Analyzer Change exit point, you use the following command:
ADDEXITPGM EXITPNT(QIBM_QCA_CHG_COMMAND) FORMAT(CHGC0100) PGMNBR(*LOW)
PGM(VINING/CHKKILL) PGMDTA(*JOB 20 'ENDJOB QSYS ')
Running this Add Exit Program (ADDEXITPGM) command will cause the system to immediately start calling the exit program VINING/CHKKILL whenever the IBM-provided ENDJOB command is about to be run.
The exit point QIBM_QCA_CHG_COMMAND is a system-provided exit point that allows a user exit program to change a command prior to the running of the command. The PGMDTA parameter defines which command you are registering for. In your case, it's the ENDJOB command, which is entered as a 10-byte, blank padded value. The library where the ENDJOB command is located is also entered as a 10-byte, blank padded value immediately following the command name. Incidentally, there is also the exit point QIBM_QCA_RTV_COMMAND, which you can use to have a user exit program be called when a command might be run, but it doesn't provide the capability to change the command. A very handy capability for selective command auditing!
The above ADDEXITPGM command also tells the exit point QIBM_QCA_CHG_COMMAND that the information passed to the CHKKILL program should conform to format CHGC0100, the format we discussed in the previous article.
With that out of the way, now let's look at the flow of the CHKKILL program.
Upon entry to the program, CHKKILL first attempts to verify that it is being called correctly. This is done by first checking to see if the exit point name, &EP_Name of the &Chg_Info parameter, is QIBM_QCA_CHG_COMMAND. If it isn't, CHKKILL sends the escape message PRX1001 to the caller of CHKKILL. The PRX1001 message was created in the last article and has three replacement variables. One variable, &ErrEP_Name, is used to indicate what exit point called CHKKILL in error. The actual sending of the escape message (which is identified by the variable &MsgID) and the setting of the other two replacement variables is done by the SndErrMsg subroutine.
Now the first thing that might go wrong with CHKKILL is that it might be called incorrectly with no parameters at all. This could happen if someone simply went to the command line and entered CALL CHKKILL. In this situation, the "If" check for &EP_Name would fail with the escape message MCH3601 - Pointer not set for location referenced. In this case, the location referenced is variable &EP_Name as the &Chg_Info parameter was not passed, and the variable &EP_Name is defined as a subfield of the &Chg_Info parameter. To prevent CHKKILL from ending abnormally due to the MCH3601 message, we have a global MonMsg declared, monitoring for any CPF or MCH escape message. If this monitor gets control, then control is passed to the label CatchAll, which in turn sends the message PRX1099 using the SndErrMsg subroutine. This message simply tells the user that CHKKILL ended unexpectedly and directs the user to see previous messages in the job log for the reason.
The SndErrMsg subroutine represents shared logic related to the sending of PRXxxxx escape messages. All of the PRXxxxx messages have replacement variables &1 and &2, which represent the sending program name and program library name, respectively. So the first objective of the SndErrMsg subroutine is to get this program-related information. It does this by using the Materialize Program Name machine interface (MI) instruction MATPGMNM, which is documented here. The MATPGMNM instruction returns the bound program name, bound service program, or Java program in which the current procedure is running. Note that while there are other ways to obtain the currently running program name (sending a message, receiving the message, and determining who originally sent the message--which would be your program; retrieving the call stack and finding your entry; etc.), MATPGMNM will by far be the fastest.
The MATPGMNM instruction has one parameter. In the CHKKILL program, we have defined the relevant fields of this parameter with the structure &Pgm_Struct. If you look at the &Pgm_Struct structure, you may notice a few things that seem to be wrong--namely, the program and library name are defined as being 30 bytes long! This is the correct definition. At the MI, object names can indeed be 30 bytes. It is the operating system that, for the most part, only allows you to use 10-byte names. In addition, if you look at the MI instruction documentation, you will see several uses of the word "context." At the MI, "context" is essentially the same as what we normally think of as a library.
The structure &Pgm_Struct is used for both input and output to the MATPGMNM instruction. On input, you provide the size of the structure in the field &BytPrv (Bytes provided), and MATPGMNM returns to you those fields of &Pgm_Struct that will fit within the number of bytes you provided. As an input to the instruction, &Pgm_Struct also has some reserved fields, and these fields need to be initialized to x'00'. Rather than typing in initial values (the VALUE keyword on the DCL CL statement) of x'00' for the various fields, I prefer to simply set the entire structure to x'00'. CHKKILL does this using the Propagate Byte MI instruction PROPB, which is documented here. PROPB will propagate a given character value across as many bytes as you want. In the case of CHKKILL, the statement below moves &Size_MatPG x'00's to the variable &Pgm_Struct.
CallPrc Prc('_PROPB') Parm((&Pgm_Struct) +
(x'00' *ByVal) (&Size_MatPG *ByVal))
And just what is the value of &Size_MatPG? It is set to the allocated size (80 bytes) of &Pgm_Struct due to the immediately preceding RtvVarSiz (Retrieve Variable Size) command:
RtvVarSiz Var(&Pgm_Struct) Size(&Size_MatPG)
As a reminder, if you do not have the RtvVarSiz command on your system, refer to the article "Just How Big Is That Variable?" for the command definition and command processing source.
As a tip, you can use PROPB anytime you need to move the same character repetitively into a variable. For instance, if you wanted to move asterisks ('*') into the first 20 bytes of variable &TextLine, you could do this:
CallPrc Prc('_PROPB') Parm((&TextLine) +
('*' *ByVal) (&Size_TL *ByVal))
Here, &Size_TL is defined as a 4-byte integer with a value of 20. This certainly beats having to type in 20 asterisks and then count them umpteen times to make sure you have exactly 20! Likewise, if you wanted to move asterisks into the 20 bytes of variable &TextLine starting at position 30, you could do this:
CallPrc Prc('_PROPB') Parm((&Ptr_TL *ByVal) +
('*' *ByVal) (&Size_TL *ByVal))
Here, &Ptr_TL is a pointer whose value addresses the 30th position (an offset value of 29) of &TextLine. You can also use a variable for the second parameter of PROPB rather than a literal value such as '*'. In this case, the variable would be defined as a *CHAR field with a length of 1.
Having initialized &Pgm_Struct to x'00's, SndErrMsg then sets the &BytPrv field to the allocated size of &Pgm_Struct and runs the MATPGMNM instruction. SndErrMsg then sets the PRXxxxx &1 and &2 message replacement variables to the first 10 bytes of the returned program and program library name, respectively, and sends the escape message identified by the variable &MsgID. Being an escape message, CHKKILL will not regain control after the Send Program Message (SNDPGMMSG) command runs. The program will have ended.
When sending the escape message, SndErrMsg passes the structure &ErrMsgDta for replacement data associated with each message. In looking at &ErrMsgDta, you will notice that all of the unique replacement variables for the PRXxxxx messages start at position 21 of the structure. CHKKILL is using the V5R4 STG(*DEFINED) support to redefine positions 21 through 40 to the appropriate data type for unique replacement values that are associated with each error message.
Having looked at SndErrMsg, we will now return to the mainline of CHKKILL.
After verifying that CHKKILL was called by the exit point QIBM_QCA_CHG_COMMAND, CHKKILL then also validates that the Change command exit information parameter does conform to format CHGC0100 and that the command being processed is ENDJOB. If either validation fails, an appropriate error message is sent, again using the SndErrMsg subroutine.
At this point, we are ready to start processing the Change command exit information data associated with the ENDJOB command to be run. This processing will be the focus of the next article as I'm already about 1000 words over the suggested size of an article....
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online