Learn what the Change Library List API can do for you.
I recently received a note from Shawn W. related to a situation in which he needs to significantly change a job's library list, perform a function, and then restore the job's library list to the original values. In his case, a corporation on one System i (IBM i, iSeries) might have multiple instances of an entity (multiple plants, multiple companies, etc.) with each entity having multiple libraries associated with it (inventory libraries, financial libraries, etc.). When corporate user X, who might work primarily with entity Y, wants to perform a quick query of entity Z, the need is to save the current library list (presumably, but not guaranteed, oriented to work with entity Y), manipulate the library list to set an environment oriented to entity Z while maintaining other (corporate or facility related) libraries currently in the library list, call a reporting function to report on Z, and on return from the reporting function restore the job environment to the original entity (Y). There are many ways to address these requirements but, as this is The API Corner, we'll approach this task today with the use of several system APIs.
Following is the program we'll be using today. Assuming that you have stored the below source in member SETLIBS of source file QRPGLESRC, you can create the program using the command CRTBNDRPG PGM(SETLIBS).
h dftactgrp(*no)
dSetLibs pr
d Lib1_In 10a const
d Lib2_In 10a const
dSetLibs pi
d Lib1_In 10a const
d Lib2_In 10a const
d AddLib pr
d Lib 10a const
d ChgLibl pr extpgm('QLICHGLL')
d CurLib 11a const
d ProdLib1 11a const
d ProdLib2 11a const
d UsrLibl 10000a options(*varsize)
d NbrUsrLibl 10i 0 const
d ErrCde likeds(QUSEC)
d RmvLib pr
d Lib 10a const
d RtvJobI pr extpgm('QUSRJOBI')
d RcvVar 1a options(*varsize)
d LenRcvVar 10i 0 const
d Format 8a const
d QualJobName 26a const
d IntJobID 16a const
d ErrCde likeds(QUSEC) options(*nopass)
d ResetPfrDta 1a const options(*nopass)
d RunCmd pr extpgm('QCAPCMD')
d SrcCmdStr 4096a const options(*varsize)
d LenSrcStr 10i 0 const
d OptCtlBlk 4096a const options(*varsize)
d LenCtlBlk 10i 0 const
d Format 8a const
d ChgCmdStr 1a options(*varsize)
d LenChgStr 10i 0 const
d LenRtnChgStr 10i 0
d ErrCde likeds(QUSEC)
d SndPgmMsg pr extpgm('QMHSNDPM')
d MsgID 7a const
d QualMsgF 20a const
d MsgDta 256a const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10a const
d CSE 10a const
d CSECtr 10i 0 const
d MsgKey 4a
d ErrCde likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20a const options(*nopass)
d DspMsgWait 10i 0 const options(*nopass)
d CSEType 10a const options(*nopass)
d CCSID 10i 0 const options(*nopass)
d LibInfo ds qualified
d Hdr likeds(QUSI0700)
d Libs 10000a
d ErrCde ds qualified
d Hdr likeds(QUSEC)
d MsgDta 256a
d AnotherLib s 10a
d ChgCmdStr s 1a
d CurLib s 10a
d LenChgStr s 10i 0
d LiblEnt s 11a based(LiblEntPtr)
d LiblEntPtr s *
d Msg s 512a
d MsgKey s 4a
d NA s 10a
d SrcCmdStr s 4096a varying
d X s 10i 0
/copy qsysinc/qrpglesrc,qcapcmd
/copy qsysinc/qrpglesrc,qusec
/copy qsysinc/qrpglesrc,qusrjobi
/free
// Set API QUSEC parameter to send exceptions
QUSBPrv = 0;
// Set API ErrCde parameter to not send exceptions
ErrCde.Hdr.QUSBPrv = %size(ErrCde);
// Set QCAPCMD Options Control Block for running CL commands
QCAP0100 = *Allx'00';
QCACmdPT = 0;
QCABCSDH = '0';
QCAPA = '0';
QCACmdSS = '0';
QCAMK = *blanks;
QCASIDCS = 0;
// Get the current library list
RtvJobI(LibInfo :%size(LibInfo) :'JOBI0700' :'*' :' ' :QUSEC);
CurLib = %subst(LibInfo.Libs
:(((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL) *
11) + 1)
:10);
// Set current library to Lib1_In
ChgLibl(Lib1_In :'*SAME' :'*SAME' :NA :-1 :ErrCde);
if ErrCde.Hdr.QUSBAvl <> 0;
Msg = 'Unable to assign curlib of ' + Lib1_In;
SndPgmMsg('CPF9897' :'QCPFMSG *LIBL'
:Msg :%len(%trimr(Msg))
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
RmvLib(Lib2_In);
AddLib(Lib2_In);
AnotherLib = 'QGPL';
RmvLib(AnotherLib);
AddLib(AnotherLib);
// Restore the user portion of the *LIBL to original values
LiblEntPtr = %addr(LibInfo.Libs) +
((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +
LibInfo.Hdr.QUSCL01) * %size(LiblEnt));
ChgLibl(CurLib :'*SAME' :'*SAME'
:LiblEnt :LibInfo.Hdr.QUSLIU :ErrCde);
if ErrCde.Hdr.QUSBAvl <> 0;
Msg = 'Unable to restore previous library list';
SndPgmMsg('CPF9897' :'QCPFMSG *LIBL'
:Msg :%len(%trimr(Msg))
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
*inlr = *on;
return;
/end-free
**********************************************************************
p RmvLib b
d RmvLib pi
d Lib_In 10a const
/free
for X = 1 to LibInfo.Hdr.QUSLIU;
// Check if Lib_In in library list and, if so, remove it
if X = 1;
LiblEntPtr = %addr(LibInfo.Libs) +
((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +
LibInfo.Hdr.QUSCL01) * %size(LiblEnt));
else;
LiblEntPtr += %size(LiblEnt);
endif;
if LiblEnt = Lib_In;
leave;
endif;
endfor;
if X <= LibInfo.Hdr.QUSLIU;
// Lib_In currently in *LIBL, need to remove
SrcCmdStr = 'RmvLibLE Lib(' +
%trimr(Lib_In) + ')';
RunCmd(SrcCmdStr :%len(SrcCmdStr)
:QCAP0100 :%size(QCAP0100) :'CPOP0100'
:ChgCmdStr :0 :LenChgStr :ErrCde);
if ErrCde.Hdr.QUSBAvl <> 0;
Msg = 'Unable to remove library list entry ' + Lib_In;
SndPgmMsg('CPF9897' :'QCPFMSG *LIBL'
:Msg :%len(%trimr(Msg))
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
endif;
/end-free
p RmvLib e
**********************************************************************
p AddLib b
d AddLib pi
d Lib_In 10a const
/free
SrcCmdStr = 'AddLibLE Lib(' +
%trimr(Lib_In) + ') Position(*First)';
RunCmd(SrcCmdStr :%len(SrcCmdStr)
:QCAP0100 :%size(QCAP0100) :'CPOP0100'
:ChgCmdStr :0 :LenChgStr :ErrCde);
if ErrCde.Hdr.QUSBAvl <> 0;
Msg = 'Unable to add library list entry ' + Lib_In;
SndPgmMsg('CPF9897' :'QCPFMSG *LIBL'
:Msg :%len(%trimr(Msg))
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
/end-free
p AddLib e
The SETLIBS program is passed two parameters. The first parameter, Lib1_In, is the library that should be used as the job's current library when running the report. The second parameter, Lib2_In, is a library that should be near the top of the job's library list when running the report. As we will see shortly, SETLIBS may also insert additional libraries, prior to Lib2_In, into the current job's library list.
After initializing various API-related variables (an API error code structure QUSEC to have API-detected errors returned as exceptions, an API error code structure ErrCde to have API-detected errors returned in the error code structure, and the control block structure CPOP0100 of the Process Commands (QCAPCMD) API to run CL commands) to enable the environment we want SETLIBS to run in, we start our actual processing.
The first thing we need to do is save the current library list. To accomplish this, we'll use an API that many of you have most likely used in the past: Retrieve Job Information (QUSRJOBI). In previous articles, such as "Easily Identify What Message a Job Is Waiting On," we saw how format JOBI0200 of QUSRJOBI returns information such as if a job is waiting on a reply to a specific message and, if so, what message and where the message can be found. Today, we'll look at format JOBI0700, which returns library list information for an active job. Note that while QUSRJOBI does support other library-related formats, such as JOBI0750, all of the information we'll need today is in JOBI0700, the simpler (and faster) of the library list formats.
The JOBI0700 format returns the following structure.
Offset |
Type |
Field |
|
Dec |
Hex |
||
0 |
0 |
BINARY(4) |
Number of bytes returned |
4 |
4 |
BINARY(4) |
Number of bytes available |
8 |
8 |
CHAR(10) |
Job name |
18 |
12 |
CHAR(10) |
User name |
28 |
1C |
CHAR(6) |
Job number |
34 |
22 |
CHAR(16) |
Internal job identifier |
50 |
32 |
CHAR(10) |
Job status |
60 |
3C |
CHAR(1) |
Job type |
61 |
3D |
CHAR(1) |
Job subtype |
62 |
3E |
CHAR(2) |
Reserved |
64 |
40 |
BINARY(4) |
Number of libraries in SYSLIBL |
68 |
44 |
BINARY(4) |
Number of product libraries |
72 |
48 |
BINARY(4) |
Current library existence |
76 |
4C |
BINARY(4) |
Number of libraries in USRLIBL |
See note |
See note |
Array(*) of CHAR(11) |
System library list |
See note |
See note |
Array(*) of CHAR(11) |
Product libraries |
See note |
See note |
Array(*) of CHAR(11) |
Current library |
See note |
See note |
Array(*) of CHAR(11) |
User library list |
Note: The decimal and hexadecimal offsets depend on the number of libraries you have in the various parts of your library lists. The data is left-justified with a blank pad at the end. The array is sequential. It is an array or data structure. See the Control language topic collection for the total number of libraries that can be returned to you. |
As you can see, the API returns all of the libraries in the system library list of the job, the product libraries of the job, the current library of the job, and the libraries in the user library list of the job. The libraries are returned as Char(11) array elements with the first array element immediately following the Binary(4) Number of libraries in USRLIBL field. Having the libraries returned as Char(11) fields rather than Char(10) is a bit unusual but, as you'll see soon, is also quite convenient when later working with the Change Library List API.
When calling the QUSRJOBI API, SETLIBS uses the LibInfo data structure as the receiver variable. The LibInfo data structure is defined as being LIKEDS the QSYSINC-provided data structure QUSI0700, which defines the fixed-location fields shown above. Following the LIKEDS definition is the field Libs, which is defined as Char(10000), an arbitrary size that is sufficiently large to hold all library names that might be found in the jobs system library list, product library list, current library, and user library list.
To demonstrate how the returned library information might be accessed, SETLIBS next locates and saves the job's current library to the field CurLib. The job's current library is located, relative to the starting location of the returned library list (LibInfo.Libs), by summing the number of libraries in the system portion of the library list (LibInfo.Hdr.QUSLIS) and the number of product libraries (LibInfo.Hdr.QUSPL) and then multiplying this value by the size of each library name returned (11). The resulting displacement into LibInfo.Libs is then used in a %SUBST operation to access the first 10 bytes, which represents the current library value.
A couple of points related to this access to the current library: First, SETLIBS doesn't really need to assign the field CurLib to the value of the job's current library. The use of CurLib is done solely to demonstrate how to navigate the returned list of libraries. Second, CurLib is defined as a Char(10) field within SETLIBS even though both the Retrieve Job Information and the Change Library List APIs use a Char(11) definition. This is done as most applications do work with library names using a Char(10) definition rather than Char(11). When later using CurLib to restore the current library of the job (after our application processing is done), this difference in definition is taken care of due to the prototype of Change Library List defining the current library parameter as a Char(11) constant.
Having saved the current library to CurLib, SETLIBS now changes the current library by calling the Change Library List (QLICHGLL) API. Prototyped as ChgLibl, the QLICHGLL API defines six parameters:
- The new library to be used as the job's current library, defined as a Char(11) input
- The new library to be used as the job's first product library, defined as a Char(11) input
- The new library to be used as the job's second product library, defined as a Char(11) input
- An array of library names to be used in setting the job's user library list, with each array element defined as a Char(11) and the parameter used as input
- The number of library names being passed in the fourth parameter, defined as a Binary(4) input
- The standard API error code structure
Several of the QLICHGLL parameters support special values, one special value in particular corresponding to "do not change." In the initial call to the Change Library List API, as all we want to change is the current library, the following call indicates that no change should be made to the product libraries or the user library list of the job. The only change is that the current library should be set to the value of variable Lib1_In (which, as a reminder, is an input parameter to the SETLIBS program).
ChgLibl(Lib1_In :'*SAME' :'*SAME' :NA :-1 :ErrCde);
After verifying that no error was encountered when changing the current library, SETLIBS then makes sure that the second parameter passed to the program, Lib2_In, will be a library near the top of the job's library list when running the report. It accomplishes this by first calling the procedure RmvLib to make sure that Lib2_In isn't currently in the user library list, and then procedure AddLib to add Lib2_In to the top of the current user library list.
The RmvLib procedure could be implemented using a variety of approaches. One that I've run across often is calling a CL program that unconditionally attempts to remove the library using the RMVLIBLE command and then monitoring for the library not being in the user library list with MONMSG. An API-oriented variant of this would be using the QCAPCMD API to run the RMVLIBLE command with an Error Code parameter having a non-0 Bytes provided value (mimicking the CL MONMSG command). In the case of SETLIBS, we have the opportunity to do this processing much more efficiently.
In LibInfo.Libs, SETLIBS already has the full list of library in the job's user library list, so, rather than attempting a RMVLIBLE and then monitoring for a failure, RmvLib enters a FOR group that looks to see if the library to be removed, Lib_In, is indeed in the user library list. Only if Lib_In is found in the current library list does the program run the RMVLIBLE command.
The FOR group processing starts with the first library in the user library list and is constrained by the number of libraries in the user library list (LibInfo.Hdr.QUSLIU). To access the first library in the list (that is, when X is set to 1, indicating the initial iteration of the FOR group), RmvLib sets the pointer LiblEntPtr to the address of the first library. This address is determined in a manner quite similar to how the current library was earlier found. RmvLib takes the address of LibInfo.Libs; sums the number of libraries in the system portion of the library list (LibInfo.Hdr.QUSLIS), the number of product libraries (LibInfo.Hdr.QUSPL), and the number of current libraries (either 0 or 1); and then multiplies this value by the size of each library name returned (11). The resulting pointer is then used with a based variable (LiblEnt) to access the first library. To access subsequent libraries in the user library list (that is, when X > 1) RmvLib simply adds the size of one library entry (11) to the pointer LiblEntPtr in order to access the next library list entry.
Within the FOR group, if LiblEnt is equal to Lib_In, then Lib_In is in the current library list and the FOR group is exited. Upon exit from the FOR group, if X is less than or equal to the number of entries in the library list, then QCAPCMD is used to remove Lib_In from the job's library list; otherwise, RmvLib simply returns.
Having ensured that the library to be added to the top of the current library list does not currently exist in the job's library list, SETLIBS then runs the AddLib procedure. AddLib simply adds the library to the top of the library list using the ADDLIBLE CL command and the QCAPCMD API.
Following this, SETLIBS arbitrarily adds library QGPL to the user library list. This is where Shawn W.'s production application might determine, using whatever logic is appropriate for the environment, whether any additional libraries might be needed. Each of these additional libraries can be added by simply using the RmvLib and AddLib procedures for each library.
Having added all necessary libraries, SETLIBS would now call the reporting tool that is dependent on the library list environment we have created.
After the reporting tool has returned, SETLIBS needs to restore the original library environment prior to returning control to the user. This is accomplished quite easily with the two following operations:
LiblEntPtr = %addr(LibInfo.Libs) +
((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +
LibInfo.Hdr.QUSCL01) * %size(LiblEnt));
ChgLibl(CurLib :'*SAME' :'*SAME'
:LiblEnt :LibInfo.Hdr.QUSLIU :ErrCde);
LiblEntPtr is set to address the first library in the original user library list, and the QLICHGLL API is called. The parameters passed to the API are the name of the original current library, the specification that no change is needed to the product libraries (as SETLIBS didn't change them), and the specification that the user library list should be set to the original library list starting with LiblEnt (which is based on LiblEntPtr) for the number of libraries in the original library list (LibInfo.Hdr.QUSLU).
We now have a rather efficient method for manipulating, and later restoring, a job's library list. If, for instance, corporate user X, while working with entity Y, needs to perform a quick query of entity Z, this can be accomplished with CALL PGM(SETLIBS) PARM('ZLIB1' 'ZLIBX'), where ZLIB1 and ZLIBX represent two of the libraries necessary for reporting on entity Z. SETLIBS itself may determine that, like how we added QGPL, libraries ZLIBOTHER, ZLIBINVEN, and ZLIBAP are also needed. The application could then use the RmvLib/AddLib procedures to do this.
As usual, if you have any API questions, send them to me at
LATEST COMMENTS
MC Press Online