Surely, you have noticed that each time you run a command incorrectly, OS/400 gets back to you with an error message of some sort. For example, you may want to display the contents of library XYZ with the Display Library (DSPLIB) command, as follows:
DSPLIB XYZ
If you don't have a library named XYZ, however, OS/400 tells you "Library XYZ not found" via a programmessage. In fact, most OS/400 activities produce messages of some sort, and not just for errorconditions. There are many types of messages, but the four types that stand out are informational(*INFO), completion (*COMP), diagnostic (*DIAG), and escape (*ESCAPE). In many cases, these messagescarry important information.
The problem is that, whereas running DSPLIB from the command line makes any error message immediatelyvisible, running the same command from a CL program hides the message. The reason is that OS/400 sendsthe message to the caller of the program that contains the command in error; if you run the commandfrom the command line, the command's CPP sends the error message to its caller (the command line). Ifyou run the command from a CL program, it's the CL program, not the command line, that gets themessage.
It's easy to see, therefore, the importance of forwarding messages up the call stack. In this article,I present a utility command that makes forwarding such program messages very simple and convenient.The call stack is a last-in, first-out queue (or "stack") that contains an ordered list of all theprograms that a particular job is running. At the top of the call stack, you'll find program QSYS/QCMDin most jobs. Then, as programs are called, they are added at the bottom of the stack.
Figure 1 shows a typical call stack. Let's suppose you're running an interactive job. Call stack level1 contains QCMD. Then, you call OPM program A directly from the command line; at that point, the callstack contains program A in level 2. Program A then calls ILE program B, which contains two procedures-procedure 1 and procedure 2, in such a way that procedure 1 calls procedure 2. Finally, procedure 2calls OPM program C.
When any of these programs and procedures ends in error, the error message must be forwarded up thecall stack so that the caller becomes aware of the error condition and can take corrective action (ormerely pass the bucket further up the call stack). For example, if OPM program C ends in error, ILEprocedure 2 gets the error message. The procedure can then either take corrective action or forward theerror message to its caller, ILE procedure 1.
The problem, then, is what procedure 1 should do about it. If it cannot take corrective action, itshould forward the message to its own caller. But its caller is OPM program A, which is two levels upthe stack; OS/400 inserted a procedure of its own, called _CL_PEP, inserted by the CL compiler. Thename stands for "CL Program Entry Procedure," and all ILE CL programs have one.
Over the years, I've seen dozens of variations of message-forwarding algorithms. The simplest involvesa Receive Message (RCVMSG) command and a Send Program Message (SNDPGMMSG) command, which forward to thecaller the escape message with which a CL program has ended in error. Although simple to code, it's adeficient technique, because often the escape message doesn't contain enough information to find outwhat went wrong.
A better technique involves the use of two APIs: QMHMOVPM and QMHRSNEM. The first API moves to thecaller any diagnostic messages, and the second API resends the escape message. This technique isbetter, but coding an API call is not something you can do with your eyes closed.
These difficulties led me to develop the Forward Program Messages (FWDPGMMSG) command (Figure 2). Its purpose is to facilitate coding the message-forwarding algorithm as much as possible:
- You code only two lines of code.
- You don't need any variables, so you can't forget any Declare (DCL) commands.
- You always code the same two lines, regardless of CL program type (OPM or ILE).FWDPGMMSG has only two parameters, both of which accept default values:o Message type (MSGTYPE). With this parameter, you tell FWD-PGMMSG which messages you want to forwardto the caller. There are only three valid values: *DIAGESC (default), *INFOCOMP, and *ALL. *DIAGESCforwards diagnostic and escape messages only; *INFOCOMP, informational and completion messages only;*ALL, all four message types.
- Convert escape to diagnostic (CVTESCAPE). Sometimes, you want to forward messages to the caller insuch a way that any escape messages don't interfere with the smooth operation of the caller. To do that, you must convert any escape messages to diagnostic while forwarding. FWDPGMMSG makes this easy ifyou specify *YES for this parameter. Or you can leave the default value, *NO, if you want escapemessages to remain as escape messages.
FWDPGMMSG can be used in any CL program or CL procedure. In most cases, you'll want to forward errormessages to the caller, so you can take the default values for both parameters and write your CL sourcecode as illustrated in Figure 3.
First, your CL program or procedure must contain a global Monitor Message (MONMSG) command, branchingcontrol to tag ERROR if any unexpected CPF, MCH, CEE, or other messages show up. You should at leastmonitor for CPF messages, but think of any others that may pop up. As usual, the global MONMSG must sitbetween the DCLs and the normal processing (or "body") of the program. Second, code a RETURN command atthe end of the body of the program, immediately followed by label ERROR, which in turn should befollowed by the message-forwarding mechanism. Finally, code a FWDPGMMSG with no parameters and a MONMSGto monitor CPF0000 (any CPF messages).
FWDPGMMSG without parameters will forward any diagnostic and escape messages to the CL program'scaller, without converting the escape message to diagnostic. The caller can then monitor for errormessages immediately after the CALL (or CALLPRC) command and take corrective action-or forward themessages further up the call stack.
It's very important that you code a MONMSG for CPF0000 immediately after FWDPGMMSG. If FWDPGMMSG wereto fail for any reason and you had omitted this MONMSG, your CL program would enter an infinite loop.The reason is simple: If FWDPGMMSG fails and there's no MONMSG, the global MONMSG traps it-and branchescontrol to tag ERROR, where it finds FWDPGMMSG again, failing again, and repeating this process in arather tight and CPU-intensive loop. The only way out would be to press SysRq and select option 2 torun the End Request (ENDRQS) command. If the job is running in batch, you'd have to run the End Job(ENDJOB) command from a display station.
FWDPGMMSG's processing program is PGM007CL (Figure 4). It may look complicated at first glance, butit's really not that difficult to follow. To begin with, you must realize that the CPP of a command yourun within a CL program or procedure is one level below the CL program or procedure. For example,suppose that, in Figure 1, ILE procedure 1 runs the FWDPGMMSG command. The command's CPP, PGM007CL,takes up the level right below ILE procedure 1.
With this in mind, it's clear that PGM007CL must consider the program immediately above it as thesource and the one above that as the target. So, FWDPGMMSG executed from ILE procedure 1 must forwardmessages from that procedure to the caller.
But, from the call stack diagram, ILE procedure 1's caller is none other than _CL_PEP. If PGM007CL wereto send the ILE procedure 1's messages there, they would fall into a black hole and never emerge.Clearly, then, PGM007CL must determine if, at the time it is running, _CL_PEP occupies a position twolevels up the call stack. If that is the case, PGM007CL must forward the messages three levels up thestack so it reaches OPM program A.
That's the first thing PGM007CL does. It uses API QMHSNDPM to send a dummy informational message twolevels up, obtaining the 4-byte key to the message in CL variable &MSGKEY. Then, it receives the samemessage by calling QMHRCVPM, retrieving the message information in variable &RCVVAR, which is 512 byteslong and structured according to data format RCVM0300. In this format, bytes 492 to 501 of &RCVVARcontain the name of the program or procedure from which the message is being received. All I have todo, then, is compare those bytes against _CL_PEP; if equal, I set to 3 the number of levels to movemessages up; otherwise, I set that number to 2.
Next, I determine whether PGM007CL must convert escape messages to diagnostic. If not, I need to calltwo APIs: QMHMOVPM to move program messages from one program to another, and then QMHRSNPM to resendthe escape message as another escape message. Otherwise (if PGM007CL must convert escape messages todiagnostic), I call only QMHMOVPM. One of the abilities of this API is that it automatically convertsescape messages to diagnostic.
Let's look at the parameters I have coded for each of these API calls.When moving program messages without conversion, QMHMOVPM gets the following parameters:
- Four blanks for the message key. I don't supply a message key, because I want QMHMOVPM to move more than one message and to do so by message type.
- An array of message type codes, which has been set to an appropriate value beforehand. For instance,if you specified MSGTYPE(*INFOCOMP), the array will contain *INFO in the first 10 bytes and *COMP inthe following 10. The remaining 20 bytes will be blank.
- A 4-byte binary number that indicates how many message types I am providing in parameter 2.
- An asterisk, which indicates that the target program is based on the program that called the API(PGM007CL).
- Another 4-byte binary number, which indicates how many levels up to go, starting from PGM007CL.Previously, I have set this number to 2 or 3, depending on _CL_PEP.
- The API error code structure, preset to nulls.
- A number 1 expressed in binary, indicating the length of parameter 4.
- Two qualifiers for the target call stack level, both of which must be *NONE.
- The string "*CHAR," to indicate that parameter 4 contains a character value instead of a pointer.
- An asterisk, to indicate that the source program is based on the program that called the API(PGM007CL).
- A number 1 expressed in binary, indicating that the source program is one level up from PGM007CL(i.e., PGM007CL's caller).
If the message types you selected for forwarding are either *DIAGESC or *ALL and no conversion is desired for escape messages, you need to call QMHRSNEM to resend the escape message up the call stack. There are seven parameters:
- Four blanks for the message key (you don't know the key to the escape message).
- The API error code structure, preset to nulls.
- A data structure organized according to data format RSNM0100, which is 38 bytes long. This structuretells the API where to send the escape message. The components of the data structure are (a) the numberof levels to go up the stack, either 2 or 3; (b) the target call stack entry qualifiers, both set to*NONE; (c) the number 10 in binary, to indicate the length of the next component; (d) an asterisk, toindicate that the target program is based on the program that called the API (PGM007CL).
- The number 38 in binary, which is the length of the data structure given in parameter 3.
- The string RSNM0100, which identifies the organization of the structure.
- An asterisk, to indicate that the source program is based on PGM007CL.
- The number 1 in binary, to indicate that the source program is PGM007CL's caller.
I call QMHMOVPM again in the second part of PGM007CL in order to move program messages when escapemessage conversion is desired. The parameters are nearly identical, however, so I won't belabor thepoint. And before I close, I'd like to mention panel group PGM007PG (Figure 5), which provides helptext for the FWD-PGMMSG command.
Writing FWDPGMMSG was a challenge, because I had little experience with message manipulation in ILE.Besides, the IBM manuals did not mention _CL_PEP's visibility to the APIs (it is invisible to both SNDPGMMSG and RCVMSG, however), so I spent a lot of time trying different things, more or less hit andmiss, until I got the idea to insert a DSPJOB OPTION(*PGMSTK) here and there to watch the call stack atwork.
In any case, FWDPGMMSG is here. I believe you'll find it an invaluable resource for your CL programs,especially since you do not have to concern yourself over the nature (OPM or ILE) of the programs youare writing. And if you have any suggestions for improvement, I'd love to hear them.
Ernie Malaga is a technical editor for Midrange Computing. He may be reached by fax at 305-387-8263 or
by email at
OS/400 CL Programming V3R6 (SC41-4721, CD-ROM QBJAUO00)
OS/400 Message Handling APIs (SC41-4862, CD-ROM QBAKAMN01) /*===================================================================*/
/* To compile: */
/* */
/* CRTCMD CMD(XXX/FWDPGMMSG) PGM(XXX/PGM007CL) + */
/* SRCFILE(XXX/QCMDSRC) TEXT('Forward + */
/* Program Messages') ALLOW(*IPGM *IMOD + */
/* *BPGM *BMOD) HLPPNLGRP(XXX/PGM007PG) + */
/* HLPID(*CMD) */
Figure 1: Typical call stack structure
/* */
/*===================================================================*/
CMD PROMPT('Forward Program Messages')
PARM KWD(MSGTYPE) TYPE(*CHAR) LEN(9) RSTD(*YES) +
DFT(*DIAGESC) VALUES(*DIAGESC *INFOCOMP +
*ALL) PROMPT('Message types')
PARM KWD(CVTESCAPE) TYPE(*CHAR) LEN(4) RSTD(*YES) +
DFT(*NO) VALUES(*YES *NO) PROMPT('Convert +
*ESCAPE to *DIAG')
Figure 3: Using FWDPGMMSG
PGM
DCL VAR(...)
...
MONMSG MSGID(CPF0000 MCH0000) EXEC(GOTO CMDLBL(ERROR))
/* Normal processing */
RETURN
ERROR:
FWDPGMMSG
MONMSG MSGID(CPF0000)
ENDPGM /*===================================================================*/
/* To compile: */
/* */
/* CRTCLPGM PGM(XXX/PGM007CL) SRCFILE(XXX/QCLSRC) + */
/* TEXT('CPP for FWDPGMMSG command') */
/* */
/*===================================================================*/
PGM PARM(&MSGTYPE &CVTESCAPE)
DCL VAR(&APIERRCDE) TYPE(*CHAR) LEN(8) +
VALUE(X'0000000000000000')
DCL VAR(&CALLER2) TYPE(*CHAR) LEN(10)
DCL VAR(&CVTESCAPE) TYPE(*CHAR) LEN(4)
DCL VAR(&MSGKEY) TYPE(*CHAR) LEN(4)
DCL VAR(&MSGTYPE) TYPE(*CHAR) LEN(9)
DCL VAR(&MSGTYPEARR) TYPE(*CHAR) LEN(40)
DCL VAR(&NBRMSGTYPE) TYPE(*CHAR) LEN(4)
DCL VAR(&RCVVAR) TYPE(*CHAR) LEN(512)
DCL VAR(&STRUCT) TYPE(*CHAR) LEN(38)
DCL VAR(&TOPGMQ) TYPE(*CHAR) LEN(10) VALUE('*')
DCL VAR(&TOPGMQLEN) TYPE(*CHAR) LEN(4) +
VALUE(X'0000000A') /* Dec = 10 */
DCL VAR(&TOPGMQCTR) TYPE(*CHAR) LEN(4)
DCL VAR(&TOPGMQQUAL) TYPE(*CHAR) LEN(20) +
VALUE('*NONE *NONE')
/* Bridge over _CL_PEP if present */
CALL PGM(QMHSNDPM)PARM('''''Dummy'+
X'00000005' '*INFO' '*' X'00000002' +
&MSGKEY &APIERRCDE)
CALL PGM(QMHRCVPM) PARM(&RCVVAR X'00000200' +
'RCVM0300' '*' X'00000002' '*ANY' &MSGKEY +
X'00000000' '*REMOVE' &APIERRCDE)
CHGVAR VAR(&CALLER2) VALUE(%SST(&RCVVAR 492 10))
IF COND(&CALLER2 *EQ '_CL_PEP') THEN(DO)
CHGVAR VAR(%BIN(&TOPGMQCTR)) VALUE(3)
ENDDO
ELSE CMD(DO)
CHGVAR VAR(%BIN(&TOPGMQCTR)) VALUE(2)
Figure 4: CL program PGM007CL
ENDDO
/* Requested to preserve *ESCAPE messages */
IF COND(&CVTESCAPE *EQ '*NO') THEN(DO)
/* Move program messages */
IF COND(&MSGTYPE *EQ '*DIAGESC') THEN(DO)
CHGVAR VAR(&MSGTYPEARR) VALUE('*DIAG')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(1)
ENDDO
ELSE CMD(IF COND(&MSGTYPE *EQ '*INFOCOMP') THEN(DO))
CHGVAR VAR(&MSGTYPEARR) VALUE('*INFO *COMP')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(2)
ENDDO
ELSE CMD(IF COND(&MSGTYPE *EQ '*ALL') THEN(DO))
CHGVAR VAR(&MSGTYPEARR) VALUE('*INFO *COMP +
*DIAG')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(3)
ENDDO
CALL PGM(QMHMOVPM) PARM(' ' &MSGTYPEARR +
&NBRMSGTYPE '*' &TOPGMQCTR &APIERRCDE +
X'00000001' '*NONE *NONE' '*CHAR' '*' +
X'00000001')
MONMSG MSGID(CPF0000)
/* Resend escape message */
IF COND(&MSGTYPE *EQ '*DIAGESC' *OR &MSGTYPE +
*EQ '*ALL') THEN(DO)
CHGVAR VAR(&STRUCT) VALUE(&TOPGMQCTR *CAT +
&TOPGMQQUAL *CAT &TOPGMQLEN *CAT &TOPGMQ)
CALL PGM(QMHRSNEM) PARM(' ' &APIERRCDE &STRUCT +
X'00000026' 'RSNM0100' '*' X'00000001')
MONMSG MSGID(CPF0000)
ENDDO
ENDDO
/* Requested to convert *ESCAPE messages */
ELSE CMD(DO)
IF COND(&MSGTYPE *EQ '*DIAGESC') THEN(DO)
CHGVAR VAR(&MSGTYPEARR) VALUE('*DIAG *ESCAPE')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(2)
ENDDO
ELSE CMD(IF COND(&MSGTYPE *EQ '*INFOCOMP') THEN(DO))
CHGVAR VAR(&MSGTYPEARR) VALUE('*INFO *COMP')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(2)
ENDDO
ELSE CMD(IF COND(&MSGTYPE *EQ '*ALL') THEN(DO))
CHGVAR VAR(&MSGTYPEARR) VALUE('*DIAG *ESCAPE +
*INFO *COMP')
CHGVAR VAR(%BIN(&NBRMSGTYPE)) VALUE(4)
ENDDO
CALL PGM(QMHMOVPM) PARM(' ' &MSGTYPEARR +
&NBRMSGTYPE '*' &TOPGMQCTR &APIERRCDE +
X'00000001' '*NONE *NONE' '*CHAR' '*' +
X'00000001')
MONMSG MSGID(CPF0000)
ENDDO
ENDPGM
.*=====================================================================
.* To compile:
.*
.* CRTPNLGRP PNLGRP(XXX/PGM007PG) SRCFILE(XXX/QPNLSRC) +
.* TEXT('Help text for FWDPGMMSG command')
.*
.*=====================================================================
:PNLGRP.
:HELP NAME=fwdpgmmsg.
Forward Program Messages (FWDPGMMSG)
:P.
FWDPGMMSG forwards up the call stack messages of type *INFO, *COMP,
Figure 5: Panel group PGM007PG
*DIAG, and *ESCAPE. Optionally, *ESCAPE messages may be converted
to *DIAG.
:EHELP.
.*=====================================================================
:HELP NAME='fwdpgmmsg/msgtype'.
Message type (MSGTYPE) Parameter
:XH3.Message type (MSGTYPE) Parameter
:P.
Enter a special value that describes the kind of messages you want
to forward up the call stack. The valid values are&colon.
:P.
:PARML.
:PT.:PK DEF.*DIAGESC:EPK.
:PD.
Forward *DIAG (diagnostic) and *ESCAPE (escape) messages only.
:PT.*INFOCOP
:PD.
Forward *INFO (informational) and *COMP (completion) messages only.
:PT.*ALL
:PD.
Forward *INFO, *COMP, *DIAG, and *ESCAPE.
:EPARML.
:EHELP.
.*=====================================================================
:HELP NAME='fwdpgmmsg/cvtescape'.
Convert escape (CVTESCAPE) Parameter
:XH3.Convert escape (CVTESCAPE) Parameter
:P.
Enter an option that describes whether you want to convert *ESCAPE
messages to *DIAG, or leave them as *ESCAPE. The valid values
are&colon.
:P.
:PARML.
:PT.:PK DEF.*NO:EPK.
:PD.
Do not convert *ESCAPE messages to *DIAG.
:PT.*YES
:PD.
Convert *ESCAPE messages to *DIAG.
:EPARML.
:EHELP.
.*=====================================================================
:EPNLGRP.
LATEST COMMENTS
MC Press Online