Need to know when "something" has finished? The Command Analyzer Retrieve exit point capability tells you what you need to know.
This month, I had planned on looking at the DDS details of implementing the message subfile (record formats MSGSFL and MSGSFLCTL) used in last month's article, "Letting the User Know What's Right and Wrong." I am, however, going to defer that discussion due to an IBM CL command enhancement that became available just last week.
This new support allows you to designate a program that is to be called when the command processing program (CPP) of a CL command completes. Over the years, I have seen quite a few questions related to "how can I know when someone has exited from XXX"—where XXX might be a command such as Change Data (CHGDTA), Start SQL Interactive Session (STRSQL), Create Physical File (CRTPF), or a command provided by your company and/or a third party. This new support—which is available as PTFs for V5R4 (SI45987), 6.1 (SI45986), and 7.1 (SI45985)—may very well be just what the doctor ordered for many of your applications needs in this area.
As some background, starting with V4R5, IBM has supported exit point capabilities allowing you to have exit programs called before the system transfers control to the CPP of a given command. Using the Retrieve exit point, documented here, your exit program could access the command string originally submitted to the command analyzer. Using the Change exit point, documented here and utilized in the 2009 CL Corner article "Overriding Commands and Their Parameter Values," your exit program could not only access the original command string, but also change the command prior to the CPP running. With the PTFs mentioned previously, you now also have the ability for the Retrieve exit point to call an exit program after the CPP of a given command has returned control to the IBM i.
This month, we'll take an introductory look at this new after support. Next month, we'll take a more in-depth look at this new feature and see how, with the use of the existing Change exit point, we can also answer the often asked question "How can I know when a submitted job has finished?"
The Command Analyzer Retrieve exit point defines one parameter, which is passed to the exit program. This parameter is defined as a variable-length structure with the following format.
Format RTVC0100
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(2) |
Reserved |
50 |
32 |
CHAR(1) |
Before or after indicator |
51 |
33 |
CHAR(1) |
Reserved |
52 |
34 |
BIN(4) |
Offset to original command string |
56 |
38 |
BIN(4) |
Length of original command string |
60 |
3C |
BIN(4) |
Offset to replacement command string |
64 |
40 |
BIN(4) |
Length of replacement command string |
68 |
44 |
BINARY(4) |
Offset to proxy chain |
72 |
48 |
BINARY(4) |
Number of entries in proxy chain |
|
|
CHAR(*) |
Original command string |
|
|
CHAR(*) |
Replacement 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 change to this structure for the new capability of being called after the CPP has returned control is implemented at decimal offset 50. Prior to the PTFs mentioned above, there was a 4-byte reserved field starting at decimal offset 48. After PTFs are installed, this 4-byte reserved field is now defined as a 2-byte reserved field, a 1-byte field labeled "Before or after indicator," and another 1-byte reserved field. If the new "Before or after indicator" field at decimal offset 50 is set to zero (0), when the exit program is called, the call is before the CPP has run. If this new field is set to 1, the exit program call is after the CPP has run. Using this new field, you can, if you want, use previously existing command analyzer exit programs for both before and after processing.
To register an "after" exit program for a command such as CHGDTA, you can use the following command.
ADDEXITPGM EXITPNT(QIBM_QCA_RTV_COMMAND) +
FORMAT(RTVC0100) PGMNBR(*LOW) +
PGM(VINING/CHGDTAEXIT) +
PGMDTA(*JOB 30 'CHGDTA QSYS *AFTER ')
The Add Exit Program (ADDEXITPGM) command shown above is telling your i to call the program CHGDTAEXIT in library VINING after the command CHGDTA is QSYS has been run. The program data (PGMDTA) keyword value of 30 indicates that there are 30 bytes of information being supplied: a 10-byte command name (CHGDTA), a 10-byte library name (QSYS), and a 10-byte timing value (*AFTER). For compatibility prior to the PTFs, any value other than *AFTER will cause the CHGDTAEXIT program to be called before control is transferred to the CPP of the CHGDTA command.
The following source can be used to create the user program Change Data Exit (CHGDTAEXIT), using CRTBNDCL PGM(CHGDTAEXIT), assuming that you have the source stored in member CHGDTAEXIT of source file QCLSRC.
Pgm Parm(&Cmd_Info)
Dcl Var(&Cmd_Info) Type(*Char) Len(32766)
Dcl Var(&EP_Name) Type(*Char) Stg(*Defined) +
Len(20) DefVar(&Cmd_Info 1)
Dcl Var(&EP_Format) Type(*Char) Stg(*Defined) +
Len(8) DefVar(&Cmd_Info 21)
Dcl Var(&Cmd_Name) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Cmd_Info 29)
Dcl Var(&Cmd_Lib) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&Cmd_Info 39)
Dcl Var(&Reserved1) Type(*Char) Stg(*Defined) +
Len(2) DefVar(&Cmd_Info 49)
Dcl Var(&Before_Aft) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Cmd_Info 51)
Dcl Var(&Reserved2) Type(*Char) Stg(*Defined) +
Len(1) DefVar(&Cmd_Info 52)
Dcl Var(&Off_InlCmd) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 53)
Dcl Var(&Len_InlCmd) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 57)
Dcl Var(&Off_RplCmd) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 61)
Dcl Var(&Len_RplCmd) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 65)
Dcl Var(&Off_Prx) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 69)
Dcl Var(&Nbr_Prx) Type(*Int) Stg(*Defined) +
DefVar(&Cmd_Info 73)
Dcl Var(&Offset) Type(*Int)
Dcl Var(&Length) Type(*Int)
Dcl Var(&Cmd) Type(*Char) Len(100)
If Cond(&Before_Aft *EQ '1') Then(Do)
If Cond(&Off_RplCmd = 0) Then(Do)
ChgVar Var(&Offset) Value(&Off_InlCmd + 1)
ChgVar Var(&Length) Value(&Len_InlCmd)
EndDo
Else Cmd(Do)
ChgVar Var(&Offset) Value(&Off_RplCmd + 1)
ChgVar Var(&Length) Value(&Len_RplCmd)
EndDo
EndDo
If Cond(&Length *GT 100) Then( +
ChgVar Var(&Length) Value(100))
ChgVar Var(&Cmd) +
Value(%sst(&Cmd_Info &Offset &Length))
SndPgmMsg Msg(&Cmd *TCat +
' has completed') +
ToPgmQ(*Ext)
EndPgm
The provided sample program, when called by the Command Analyzer Retrieve exit point, is very general-purpose: it simply sends a message to the external message queue of the job. The message contains the command string (or at least the first 100 bytes of the command string) used when running the command—in this case, the CHGDTA command.
The majority of the program consists of Declare CL Variable (DCL) commands (from the definition of &Cmd_Info through &Nbr_Prx) that define the fields associated with format RTVC0100 of the exit point. These declares will be constant across any Command Analyzer Retrieve exit program you might write and are an excellent candidate for being put in a separate source member, which could then be included in your exit program using the Include CL Source (INCLUDE) CL command (assuming your system is at 6.1 or later).
Note the &Before_Aft conditioned Do Group (DO) shown below:
If Cond(&Before_Aft *EQ '1') Then(Do)
This is perhaps more detailed than necessary at this time, but it determines for display purposes the command and command parameters that caused the CHGDTAEXIT program to be called. As provided, the DO group checks to see if the Command Analyzer Change exit point was used to override any user-specified parameters for the command being processed. If so, the changed command and parameters are displayed rather than the initial command and associated parameters. Next month, we will look in more detail at just what is being done here—and why.
The remainder of the program simply extracts the command string and sends a message showing the command and command parameters used. If, for instance, the command CHGDTA DFUPGM(CHGFILEX)was run by the user, then after the user exits from the DFU Change Data session, the message "CHGDTA DFUPGM(CHGFILEX) has completed" will be found in the external message queue of the job. In your own program, you would most likely replace the Send Program Message (SNDPGMMSG) command with whatever logic you want run after the CHGDTA CPP has returned control to the system.
Buyer Beware
While the PTFs provide you with a very nice way to determine when a command has completed, there are a few caveats that should be pointed out.
One warning is that the exit program is not directly informed by the exit point of whether or not the command completed successfully. For example, let's say the user entered the command CHGDTA DFUPGM(CHGFILEY)and there is no DFU program named CHGFILEY. In this case, the exit program CHGDTAEXIT will still be called even though the user did not actually have the opportunity to change any data with the CHGDTA command. This is because the CHGDTA CPP will send the informational message DFU0635 (DFU could not find program CHGFILEY in library *LIBL)and then return immediately, causing the exit program CHGDTAEXIT to be called. In situations such as this, it may be appropriate for the exit program to determine whether any messages were sent by the command's CPP, a capability that can certainly be implemented using various CL message-related commands. It would surely be a nice enhancement if, in the future, the exit point could also utilize one of the reserved fields to indicate whether or not the CPP sent an escape (or similar) message, as escape messages are often used as an indication that a command has failed. Such a capability would not help in the case of CHGDTA because DFU0635 is not an escape message (which strikes me as a bit strange, but that's another story), but it would be of general assistance for many commands on the system.
A second warning is that just because the CPP of a given command returns control to the system, not all of the command processing has necessarily completed. There are, for instance, several start commands, especially in the area of communications, where the start command initiates work that is then performed asynchronously to the job running the start command. That is, just because a command such as Start TCP/IP (STRTCP) returns, it is not safe to assume that all TCP/IP interfaces are now active—only that the starting of the interfaces is in progress.
In a similar fashion, you should not be surprised to find that some commands return control to the system after initiating processing that will actually complete at a later point in the job. If you were, for example, to register the previously created CHGDTAEXIT exit program to the Sign Off (SIGNOFF) command, you might be initially surprised to see that the message "SIGNOFF has completed" is sent to the job's job log from the exit program. Without going into the details of why this is, you will find that the SIGNOFF CPP does indeed return control to the operating system—allowing in this case for your exit program to be called as part of SIGNOFF processing—prior to the job actually ending.
Thanks to Guy Vig and Jennifer Liu
This new Command Analyzer Retrieve exit point capability will be a definite boon to many application developers needing to know when "something" has finished. Next month, we'll see how this exit point enhancement can be used to determine when a submitted job has completed processing, without having to actually change the application running in the batch job. A tip of the hat to the CL team of Guy Vig and Jennifer Liu is certainly in order for providing this support not just to 7.1, but also to the previous releases of 5.4 and 6.1.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online