As an AS/400 programmer, Im well aware of the need to leave utility programs active between calls. An RPG utility program doesnt set on the LR indicator, and COBOL utility programs dont issue a STOP RUN. It is up to the programmer to make sure that such utilities are shut down properly when theyre no longer needed.
Failure to deactivate a program does not generally cause a problem. However, on more than one occasion, I have traced an error to a utility program that had been activated by another program earlier in the job.
So how do you know whether or not a utility program is still active? The Work with Job (WRKJOB) command wont tell you. The only way I could find is to use IBMs APIs. I have written a utility called List Dormant Programs (LSTDRTPGM), which uses these APIs to show all dormant programs inside a pop-up window.
Using the LSTDRTPGM Utility
LSTDRTPGM is very simple. It has only one parameter, a qualified job name, just like the WRKJOB command. Most of the time, it will be used with the default value *, which, just like the WRKJOB command, is interpreted to mean the job from which the command is being invoked. Simply typing in LSTDRTPGM with no parameters will get you the popup window, listing any programs that are dormant in memory.
In Figure 1, the first column of the default view shows the program name and the library that the program came from. The next column gives the activation group name that the dormant program is running in.
If you want more information about the dormant program, press the F11 key. Now you will see activation group number, activation number, static storage size, and program type. If you want to close the window, press either F3 or F12.
How It Works
The entire source code for the LSTDRTPGM utility is not published here, but you can download it from Midrange Computings Web site (www.midrangecomputing.com/mc/). The Command Processing Program (CPP), DRT001CL, is listed in Figure 2. The first step is to create the work files: the physical file DRT001PF and its logical file, DRT001L0, and
the physical file DRT002PD and its logical file, DRT002L0. If the work files already exist, DRT001CL clears them.
The DRT002PF file is filled by running WRKJOB OUTPUT(*PRINT) OPTION (*PGMSTK), then copying the spool file to the physical file. The next step is to find the QWVOLACT API. Program DRT004RG does the search. For some reason, the QWVOLACT API is not normally in the system part of the library list. To allow the command to run on as many systems as possible, the API is searched for if it is not in the library list. If you want to speed the command up, hardcode the addition of the needed library to the library list. Be a nice programmer and also add the code to take the library back out of the library list when the command is done.
Then DRT003RG is called. The program takes one parameter, a 150-character field, that lists all the libraries in the system part of the library list (more on why later). The first thing DRT003RG does is list the call stack. The DRT001PF physical file is filled with the output of the QWVOLACT API. Then the Shrink_Lst subroutine is executed. Shrink_Lst looks at each program in the stack. The first check is to see whether or not the program is an operating system program. Any program in the call stack from the system part of the library list is not listed as a dormant program. There are too many to list. This is why the program DRT003RG needs a list of libraries in the system part of the library list. If a call stack entry is not listed in the system part of the library list, it is not active. This is where DRT002L0 comes in. The call stack entry is compared to the list of active programs. If found, the program is skipped and not written to the subfile of dormant programs. After all records in the DRT001L0 file are analyzed, the Display_Wndw subroutine is performed to present a window listing the programs found to be dormant. If the list is empty, a message is displayed to that effect. The subfile window scrolls up and down if the list of dormant programs did not fit in the window. The function keys F3 and F12 end the program. None of the programs used by the command are left dormant.
Installing the Command
To install the command and verify the install of the command more easily, CLLE program CRT00ACL is provided. Copy each member to the appropriate source file and call CRT00ACL. The program has one optional parameter, the target library. The source files must be in the target library. If the optional parameter is not passed to the program, the library specified in the &TOOLLIB variable is used and must already exist on the system.
The next step in the install is removing any existing objects that the install creates. This step is for those who discover a problem in the middle of the install and need to reinstall. There are no confirmation messages with the deletes. Make sure there are no objects in the target library with object names that are part of the LSTDRTPGM command.
Next, the install will give each source member a description text and create the objects needed for the command. Then the install testing programs are created. There is even a COBOL install-verification program. If the install program detects the COBOL compiler, the COBOL install-testing program will be created. For systems without a COBOL compiler, creating the source file QCBLLESRC and member DRTZZZCB can be skipped. To run the install testing programs, add the library with the LSTDRTPGM command to the library list. Call DRTZZYCL. If all goes well, a window will pop up showing DRTZZZRG as a dormant program. Press F11 to see more details. Press F3 to exit.
If you have COBOL, DRTZZZCL will detect it and run the COBOL test. Just like the RPG test, the pop-up window should show the COBOL program DRTZZZCB as dormant. Press F11 to see more details. When you are finished, press F3 to exit.
Another Hole Filled
LSTDRTPGM fills another void left by OS/400. When youre testing, use LSTDRTPGM to ensure that your programs properly shut down called programs.
Figure 1: The subfile pop-up window is produced by the LSTDRTPGM command.
PGM parm(&JOB)
DCL VAR(&JOBNAME) TYPE(*CHAR) LEN(10)
DCL VAR(&JOBUSR) TYPE(*CHAR) LEN(10)
DCL VAR(&JOBNBRC) TYPE(*CHAR) LEN(6)
DCL VAR(&JOB) TYPE(*CHAR) LEN(26)
DCL VAR(&QSYSLIBL) TYPE(*CHAR) LEN(150)
DCL VAR(&RTNLIB) TYPE(*CHAR) LEN(10)
DCL VAR(&RMVLIB) TYPE(*CHAR) LEN(1) VALUE('N')
DCL VAR(&OBJ_TO_FND) TYPE(*CHAR) LEN(20) +
VALUE('QWVOLACT *ALL ') /* Object +
to find */
DCL VAR(&OBJ_TYPE) TYPE(*CHAR) LEN(10) +
VALUE('*PGM ') /* Object to find */
DCL VAR(&API_LIB) TYPE(*CHAR) LEN(10) +
VALUE('QGY') /* Name of libraray where +
API QWVOLACT is located. */
/* Variables for error-handling logic */
DCL &ERRBYTES *CHAR 4 VALUE(X'00000000')
DCL &ERROR *LGL VALUE('0')
DCL &MSGKEY *CHAR 4
DCL &MSGTYP *CHAR 10 VALUE('*DIAG')
DCL &MSGTYPCTR *CHAR 4 VALUE(X'00000001')
DCL &PGMMSGQ *CHAR 10 VALUE('*')
DCL &STKCTR *CHAR 4 VALUE(X'00000001')
MONMSG MSGID(CPF0000) EXEC(GOTO ERRPROC)
/* Split qualified job name into parts */
CHGVAR VAR(&JOBNAME) VALUE(%SST(&JOB 1 10))
CHGVAR VAR(&JOBUSR) VALUE(%SST(&JOB 11 10))
CHGVAR VAR(&JOBNBRC) VALUE(%SST(&JOB 21 6))
/* Create the work files in QTEMP */
CHKOBJ OBJ(QTEMP/DRT001PF) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801 CPF9812) EXEC(DO) /* Not Found */
RCVMSG MSGTYPE(*LAST) /* Remove automatcally +
handled message from the job log. */
RTVOBJD OBJ(DRT001PF) OBJTYPE(*FILE) RTNLIB(&RTNLIB)
CPYF FROMFILE(&RTNLIB/DRT001PF) +
TOFILE(QTEMP/DRT001PF) MBROPT(*REPLACE) +
CRTFILE(*YES)
CRTDUPOBJ OBJ(DRT001L0) FROMLIB(&RTNLIB) +
OBJTYPE(*FILE) TOLIB(QTEMP)
ENDDO
CHKOBJ OBJ(QTEMP/DRT002PF) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801 CPF9812) EXEC(DO) /* Not Found */
RCVMSG MSGTYPE(*LAST) /* Remove automatcally +
handled message from the job log. */
RTVOBJD OBJ(DRT002PF) OBJTYPE(*FILE) RTNLIB(&RTNLIB)
CPYF FROMFILE(&RTNLIB/DRT002PF) +
TOFILE(QTEMP/DRT002PF) MBROPT(*REPLACE) +
CRTFILE(*YES)
CRTDUPOBJ OBJ(DRT002L0) FROMLIB(&RTNLIB) +
OBJTYPE(*FILE) TOLIB(QTEMP)
ENDDO
CLRPFM FILE(QTEMP/DRT001PF)
CLRPFM FILE(QTEMP/DRT002PF)
/* If any of the job attributes are blank back to the default of */
/* Current Job. */
/* Output the Stack to a printer file */
IF COND(&JOBNAME *EQ '*') THEN(DO)
WRKJOB OUTPUT(*PRINT) OPTION(*PGMSTK)
ENDDO
ELSE CMD(DO)
WRKJOB JOB(&JOBNBRC/&JOBUSR/&JOBNAME) +
OUTPUT(*PRINT) OPTION(*PGMSTK)
ENDDO
CPYSPLF FILE(QPDSPJOB) TOFILE(QTEMP/DRT002PF) +
SPLNBR(*LAST)
DLTSPLF FILE(QPDSPJOB) SPLNBR(*LAST)
RTVSYSVAL SYSVAL(QSYSLIBL) RTNVAR(&QSYSLIBL)
OVRDBF FILE(DRT001PF) TOFILE(QTEMP/DRT001PF)
OVRDBF FILE(DRT001L0) TOFILE(QTEMP/DRT001L0)
OVRDBF FILE(DRT002L0) TOFILE(QTEMP/DRT002L0)
/* Look for the API in the library list. */
CHKOBJ OBJ(QWVOLACT) OBJTYPE(*PGM)
MONMSG MSGID(CPF9801) EXEC(DO)
RCVMSG MSGTYPE(*LAST) /* Remove automatcally +
handled message from the job log. */
CALL PGM(*LIBL/DRT004RG) PARM(&OBJ_TO_FND +
&OBJ_TYPE &API_LIB)
ADDLIBLE LIB(&API_LIB) POSITION(*LAST)
CHGVAR VAR(&RMVLIB) VALUE('Y')
ENDDO
/* Display the list of dormant programs. */
CALL PGM(DRT003RG) PARM(&QSYSLIBL &JOB)
GOTO CMDLBL(ENDPGM)
/*==================================================================*/
/* Error processing routine */
/*==================================================================*/
ERRPROC:
IF COND(&ERROR) THEN(GOTO ERRDONE)
ELSE CMD(CHGVAR VAR(&ERROR) VALUE('1'))
/* Move all *DIAG messages to previous program queue */
CALL PGM(QMHMOVPM) PARM(&MSGKEY &MSGTYP +
&MSGTYPCTR &PGMMSGQ &STKCTR &ERRBYTES)
/* Resend last *ESCAPE message */
ERRDONE:
CALL PGM(QMHRSNEM) PARM(&MSGKEY &ERRBYTES)
MONMSG MSGID(CPF0000) EXEC(DO)
SNDPGMMSG MSGID(CPF3CF2) MSGF(QCPFMSG) +
MSGDTA('QMHRSNEM') MSGTYPE(*ESCAPE)
MONMSG MSGID(CPF0000)
ENDDO
ENDPGM:
IF COND(&RMVLIB *EQ 'Y') THEN(DO)
RMVLIBLE LIB(&API_LIB)
ENDDO
ENDPGM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Figure 2: The main program in the LSTDRTPGM command is DRT003RG.
LATEST COMMENTS
MC Press Online