Nearly 20 years ago, I asked IBM to add the ability to create named library lists on the System/38. My reasoning was that in CL you often needed to set the library list based on the environment of the application. For example, if I was running accounting applications, I might want the accounting library list; for the order-entry application, I might need the order-entry library list.
The other day I was lurking in the Discussion Forum on my Web site, and someone asked about using an old QUSRTOOL (which is now a commercial application) to retrieve the library list of a job description. The tool didn't work any longer because, apparently, the format of the spool file of the DSPJOBD command had changed. (Are there really still tools out there that use spool files as an information resource for CL tools? Is this 1986 or 2006? But I digress.)
Anyway, the idea of retrieving the library list from a job description was compelling to me. I've had a RTVJOBD subprocedure in RPG xTools for a few years, but I never thought about using the JOBD object as a storage media for library lists.
This concept works perfectly in practice. Create a job description and assign a library list on the LIBL parameter. For example, to create an accounts payable library list in the library named QGPL, use the following CRTJOBD command:
INLLIBL(QUSRSYS QGPL QTEMP XTOOLS ISOCKETS APLIB)
I wrote a simple CL command named RTVJOBD that returns the library. To call it from CL, use the following CL statements:
DCL VAR(&LIBL) TYPE(*CHAR) LEN(2750)
DCL VAR(&LIBLCNT) TYPE(*INT) LEN(2)
MONMSG MSGID(CPF0000)
RTVJOBD JOBD(QGPL/APLIBL) RTNLIBL(&LIBL) +
RTNLIBLCNT(&LIBLCNT)
SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA(&LIBL)
ENDPGM: ENDPGM
The variable &LIBL retrieves the library list from the job description. The optional library count parameter returns the number of libraries on the library list. The CL variable &LIBLCNT is declared as a 2-byte integer and receives the count from the RTVJOBD command.
To make the RTVJOBD command work, the Retrieve Job Description API (QWDRJOBD) is called. Unfortunately, IBM foolishly inserts a named constant with the name of the API into the QSYSINC source member that contains the API's data structure. Therefore, we have to make up an alternate name to call the prototype for QWDRJOBD. I chose RTVJOBDAPI. The prototype source code for this API follows:
D*QWDRJOBD PR ExtPgm('QWDRJOBD')
D RtvJobDAPI PR ExtPgm('QWDRJOBD')
D szRtnBuffer 65535A OPTIONS(*VARSIZE)
D nRtnBufLen 10I 0 Const
D** Specify 'JOBD0100'
D apiFormat 8A Const
D JobD 20A Const
D api_error LikeDS(QUSEC)
The QWDRJOBD API returns the entire job description information, including the library list. But to make things work correctly, we have to ensure that the return structure is large enough to handle the fixed values returned by the API as well as the variable length elements, such as the library list. This means we need to either create one huge data structure or hope IBM never changes the output positions. Or we can do it the right way.
The right way is to (unfortunately) call the API twice. While I don't agree with having to do this, it is the only way to make sure the return buffer we provide to the API is large enough to receive the library list entries.
The first call to the API would be as follows:
C eval APIErrDS= *ALLX'00'
C eval APIErrDS.QUSBPRV = %size(APIErrDS)
C eval JobDInfo = *ALLX'00'
C callp RtvJobDAPI(JobDInfo : %size(JobDInfo):
C 'JOBD0100': szJOBD : APIErrDS)
This call retrieves the job description information, but the QWDD0100 data structure in the QSYSINC source member does not have space for the library list. So the library list is not returned. What is returned, however, is the so-called "Bytes Available" subfield. Since our return structure is named JOBDINFO, the subfield name is JobDInfo.QWDBAVL.
This field contains the number of bytes required to return the entire job description structure, including the library list. To make this work, we need to allocate storage for the return data. To do this, we use the %ALLOC operation, as follows:
D Based(pJobD)
C eval pJobD = %Alloc(JobDInfo.QWDBAVL)
C eval JOBD = *ALLX'00'
** Second call: Get the library list.
C callp RtvJobDAPI(JOBD : JobDInfo.QWDBAVL :
C 'JOBD0100': szJOBD : APIErrDS)
Once this part of the routine is performed, we have enough storage to hold the entire job description. The JOBD data structure is based on the QWDD0100 data structure in QSYSINC. Therefore, it does not contain library list subfields. Instead, it contains the number of library names retrieved and the offset to the list of library names.
To access the library list, I declare two based variables. One is an array of 11-byte character elements. The second is a fixed-length field with a length of 2750. These two fields overlay one another in memory (they're both based on the same pointer). Therefore, the library list is accessible either as an array or as a character string.
We have to do a little math to get to the library list. Since only the library names on the library list are returned (not the full 2750-byte library list area), we have to make sure we retrieve only the library names that are returned rather than blindly copy 250 library names. To do this, pointer math and a %SUBST built-in function are used, as follows:
D LibL S 2750A Based(pLIBL)
/free
pLibl = pJobD + JobD.QWDOILL;
rtnLibl = %subst(LIBL:1:JobD.QWDNLILL*%size(LibList));
/end-free
The first assignment calculates the location of the library list in the API return buffer. Since the field LIBL is being copied to the value returned to the CL program that calls this program, only the number of library names times 11 is returned. The second assignment using the %SUBST built-in function performs this copy.
The RTVJOBD Command
The CL command RTVJOBD wraps the code featured in this article in an easy-to-use CL command that accepts three parameters.
- JOBD—The job description whose library list is retrieved.
- RTNLIBL—A CL variable that receives the library list. This must be large enough to handle the number of libraries returned. A fixed-length CL variable of 2750 bytes is large enough for today's library maximums. If you know the library list is 25 or fewer, a CL variable with a length of 275 bytes will be good enough.
- RTNLIBLCNT—A CL variable that receives the number of library names returned. This parameter must be defined as a CL variable with TYPE(*INT) and LEN(2).
The command definition source code for the RTVJOBD command is available on the RPGIV.com downloads page. To compile it, use the following CRTCMD statement:
The RPG IV source code for the RTVJOBD program (used as the command processing program) is also available on the RPGIV.com downloads page. The compiler parameters are stored in the Header specification for this source member, so PDM option 14 or other methods should compile it without the need to prompt the CRTBNDRPG command.
The RTVJOBD command could easily be enhanced to include the ability to return any of the other attributes of the QWDRJOBD API.
Finally, after 20 years, I've found a way to store library lists in an object. The irony is that the QWDRJOBD API was introduced in OS/400 V2R2.
Bob Cozzi is a programmer/consultant, writer/author, and software developer of the RPG xTools, a popular add-on subprocedure library for RPG IV. His book The Modern RPG Language has been the most widely used RPG programming book for nearly two decades. He, along with others, speaks at and runs the highly-popular RPG World conference for RPG programmers.
LATEST COMMENTS
MC Press Online