Are you aware of what you can do with the QBNLSPGM API?
In the last article, "Module, Module, Who's Got My Module?", we wrote an application to find all *SRVPGMs that had a given *MODULE bound into them. We saw the first version of our program, MODUSAGE, but didn't have the opportunity to examine it. We'll do so now. For space reasons, the program is not being repeated in this article, so you may want to review the previous article.
After first verifying that the two required parameters, ModNam and SrchLib, were passed, MODUSAGE runs the Setup procedure. Setup performs several functions in order to set the appropriate environment for MODUSAGE.
First, the Bytes Provided field of the API error code structure QUSEC is initialized to 0. This structure was previously copied into MODUSAGE with the /copy qsysinc/qrpglesrc,qusec statement in the main procedure. By setting Bytes Provided (QUSBPRV) to 0, we are telling any API we call that we want exception messages returned to the program when an error is encountered. In a subsequent version of MODUSAGE, we will use another instance of the error code structure for when we want to handle specific error conditions and not go through the overhead of actually receiving, and monitoring for, an exception message. Because several procedures within MODUSAGE will use the QUSEC error code structure, we declare the structure within the main procedure.
Second, Setup sets the ObjLib subfield of the ObjLibNam data structure to the value of the SrchLib parameter (the library name, or special value, that was passed as the second parameter of MODUSAGE). The ObjLibNam data structure is later used when calling the QBNLSPGM API in order to identify what *SRVPGMs in the specified library are to be returned to MODUSAGE. The first 10 bytes of ObjLibNam, which represent the name of the *SRVPGM, are initialized to the special value *ALL.
Following this, Setup creates the *USRSPC MODLUSRSPC (Module List User Space) in library QTEMP. The prototype for calling the Create User Space (QUSCRTUS) API is defined in Setup. The *USRSPC is created with the following characteristics:
•· No extended attribute--We have no need for one.
•· An initial size of 1 byte--We could try to guess a size, but there isn't any need. List APIs will automatically extend a *USRSPC up to a maximum of approximately16MB if that's what's needed to accommodate the data being returned. The initial size parameter is more for other uses of *USRSPCs, users who are not using list APIs.
•· An initial value of x'00' for the storage associated with the *USRSPC--This is, per the API documentation, the best-performing value.
•· A public authority of *CHANGE--This doesn't mean a whole lot as the *USRSPC is being created in QTEMP.
•· No text description--As with an extended attribute, we just don't have a need for one. If we were creating the *USRSPC into a permanent library, such as QGPL, a text description could be more valuable.
•· An option to replace any existing *USRSPC by the same name in the same library--Due to the overhead of creating an object, this is one option we may revisit later (though not today).
The QUSEC error code structure is used when calling the QUSCRTUS API so that any errors will cause an exception message to be returned to the program. The documentation for the QUSCRTUS API can be found here.
After successfully creating the *USRSPC, Setup then sets the pointer variable ModLstSpcPtr (Module List Space Pointer), defined in the main procedure, to the first byte of the *USRSPC MODLUSRSPC. This is done by calling the Retrieve Pointer to User Space (QUSPTRUS) API. The prototype for calling QUSPTRUS is defined in Setup, and the documentation for the API can be found here.
Setup then returns to the main procedure, where the ChkSrvPgmMod (Check Service Program Modules) procedure is run. ChkSrvPgmMod first calls the List Service Program Information (QBNLSPGM) API. The first parameter value, ModLstSpc, tells the API to return the requested data in the MODLUSRSPC *USRSPC previously created in Setup. The second parameter value, SPGL0100, identifies the type of information to be returned; format SPGL0100 indicates that information about bound *MODULEs is being requested. The third parameter, ObjLibNam, identifies what *SRVPGMs we're interested in. As a reminder, this variable was previously set in Setup and specifies that *ALL *SRVPGMs in the library previously passed to MODUSAGE should be returned. The fourth parameter is the QUSEC error code structure.
Now things get a little more interesting!
With list-type APIs, there is a common header, often called the generic header, which the API provides at the start of the *USRSPC. The documentation for this generic header can be found here. In the main procedure of MODUSAGE, we included the IBM-supplied definition for this generic header with the statement /copy qsysinc/qrpglesrc,qusgen. While there are many fields of interest in this generic header, the five of primary concern to us today are shown below:
D* Qus Generic Header
D QUSIS 104 104
D* Information Status
D QUSOLD 125 128B 0
D* Offset List Data
D QUSNBRLE 133 136B 0
D* Number List Entries
D QUSSEE 137 140B 0
D* Size Each Entry
The first field of interest is the name of the data structure, QUSH0100. In MODUSAGE, we referenced this data structure when defining our own data structure GenHdr, like so:
dGenHdr ds likeds(QUSH0100)
As GenHdr is based on the ModLstSpcPtr, ModLstSpcPtr is addressing the first byte of the *USRSPC (this was done in the Setup procedure), and the QBNLSPGM API stores the generic header starting at the first byte of the *USRSPC, we're ready to start using the subfields of the generic header as soon as the QBNLSPGM API returns to our application.
The next statement, following the call to QBNLSPGM, is to determine if the generic header Information status subfield, QUSIS, has the value 'C'. QUSIS provides the status of the information within the returned list. A value of 'C' indicates the list is complete and accurate, 'P' that the list is incomplete (there may be more list entries than could fit in one *USRSPC) but accurate in terms of what is returned, and 'I' that the returned information is incomplete--essentially, do not process any list entries. This status field should be examined prior to using any other generic header field that is related to the returned list.
With some list-type APIs, though not QBNLSPGM, there is the capability to call the API multiple times with an option for the API to resume where it left off, on the previous call, in terms of returning list entries to the *USRSPC. In these cases, a QUSIS value of 'P' indicates that you need to call the API one or more additional times in order to access all available information. The List Save File (QSRLSAVF) API documented here, with its Continuation handle parameter, is an example of such an API. QBNLSPGM does not provide this "continuation" capability, so MODUSAGE simply checks for a QUSIS value of 'C'. If only partial information can be returned by the API due to *USRSPC size considerations, then QBNLSPGM will send the escape message CPF3CAA (List is too large for user space MODLUSRSPC), and because the QUSEC error code structure is set for exceptions (and MODUSAGE isn't written to handle exceptions: no monitor, etc.), this error message would cause MODUSAGE to end without ever getting to the compare.
If GenHdr.QUSIS is 'C', then ChkSrvPgmMod accesses the first module entry in the list with this statement:
SrvPgmEPtr = ModLstSpcPtr + GenHdr.QUSOLD;
The field GenHdr.QUSOLD represents the Offset to List Data that is provided within the generic header. By adding this offset value to the starting address of the *USRSPC, ModLstSpcPtr, we determine the address of the first list entry that was returned by the API. Within the ChkSrvPgmMod procedure, we included the IBM-supplied definitions for the QBNLSPGM API formats with the statement /copy qsysinc/qrpglesrc,qbnlspgm and, similar to how we defined GenHdr, we define our own data structure SrvPgmE (Service Program Entry) using the IBM-supplied data structure QBNL010000.
dSrvPgmE ds likeds(QBNL010000)
As SrvPgmE is based on the Service Program Entry Pointer (SrvPgmEPtr) and we just set SrvPgmEPtr to the address of the first list entry, we're ready to start processing the first list entry. For the purposes of MODUSAGE, the subfields of the QBNL010000 data structure that we're interested in are these:
D* Qbn LSPGM SPGL0100
D QBNSN00 1 10
D* Srvpgm Name
D QBNSLIBN 11 20
D* Srvpgm Library Name
D QBNBMN00 21 30
D* Bound Module Name
D QBNBMLN00 31 40
D* Bound Module Library
D QBNSFILM00 61 70
D* Source File Member
In order to control when to stop processing list entries, ChkSrvPgmMod enters into a FOR loop conditioned by the Number of List Entries returned, GenHdr.QUSNBRLE. Within the FOR loop, we determine if the *SRVPGMs bound module name, SrvPgmE.QBNBMN00, is equal to the *MODULE name passed to MODUSAGE, ModNam. If so, the program displays the qualified *SRVPGM name and increments a counter, Hits, which will be used at the end of the program to indicate how many occurrences of the *MODULE were found.
Note that this comparison of the bound module name, SrvPgmE.QNBMN00, and the module name passed in as the first parameter to MODUSAGE, ModNam, is making the assumption that you do not have different modules in different libraries sharing the same name. This assumption may or may not be valid, depending on your development environment. If this assumption is not valid, it's easy enough to correct. Simply add another parameter to MODUSAGE, or extend the ModNam parameter, to include the *MODULE library name. In ChkSrvPgmMod, then compare both SrvPgmE.QNBMN00 and SrvPgmE.QBNBMLN00 (the Bound Module Library Name) to the module name and module library you have passed to MODUSAGE. In a later article, we'll make such a change when we provide a command interface to MODUSAGE. The command will support a qualified module name to search for, along with the special value *ALL for the module library name.
ChkSrvPgmMod then accesses the next list entry by adding the Size of Each Entry, GenHdr.QUSSEE, to the pointer SrvPgmEPtr and re-entering the FOR loop.
When the loop is finished, so is the program. MODUSAGE displays the total number of *SRVPGMs found that have module ModNam bound into them and then ends.
Note that this processing within ChkSrvPgmMod represents the heart and soul of how to work with list-type APIs. Once you master checking the list information status field QUSIS, accessing the first list entry with the offset to list data field QUSOLD, and processing the number of list entries, QUSNBRLE, by advancing through each entry incrementing your list pointer by the size of each entry, QUSSEE, then you're ready to take on virtually any list API. I'm confident, for instance, that you could now code the equivalent search of ILE programs using the List ILE Program Information (QBNLPGMI) API documented here.
Some list APIs will have the additional nuance of having variable list entry sizes, but even then you simply increment your list pointer by the size of each individual entry (which is always provided by the current list entry) rather than the fixed QUSSEE value (which will generally be set to 0 for formats with variable-length size entries).
Just remember that if you're using based and likeds defined structures, as we are in MODUSAGE, you must always qualify your generic header field variables with the name of your based structure. That is, GenHdr.QUSIS, GenHdr.QUSOLD, etc. rather than QUSIS, QUSOLD, etc. QUSIS, QUSOLD, and the other fields from QUSH0100 are still valid names; they'll just always be set to their initial values as they aren't being used other than for the likeds definition of our based structures.
This version of MODUSAGE isn't bad, but it isn't the greatest either. One problem is that we are making the assumption that the information associated with all modules bound into all *SRVPGMs that are analyzed per API call can fit into one *USRSPC. With the maximum size of a *USRSPC being roughly 16MB and the size of each module list entry being 3995 bytes (at least in V5R4), we're making the assumption that we will not have more than roughly 4,000 modules bound into the *SRVPGMs on our system--most likely not a good assumption.
We could try to work around this by running QBNLSPGM against each library on the system one by one, as opposed to multiple libraries when using a special value such as *ALLUSR. But we may still run into a library with more than 4,000 bound modules. As an example, IBM has over 10,000 modules bound into various QSYS *SRVPGMs in V5R4. We could start using generic *SRVPGM names (A*, B*, C*, QA*, QB*, etc.) by individual library to order to try to keep the number of modules per API call under 4,000, but there has to be a better way! And of course there is a better way.
In our next column, we'll look at an Open List API that can be used to return a list of all the *SRVPGMs on our system and that is not constrained by a size limitation of 16MB in which to return the list. Given this list of *SRVPGMs, we'll then use QBNLSPGM to list the bound modules of each *SRVPGM and analyze each *SRVPGM one by one. In this way, we can handle virtually any number of *SRVPGMs and, as long as no *SRVPGM has more than 4,000 modules bound into it (in which case I think you may have some other problems), we can find all occurrences of a *MODULE across all *SRVPGMs.
Meanwhile, if you have other API questions, send them to me at firstname.lastname@example.org. I'll see what I can do about answering your burning questions in future columns.