APIs by Example: Retrieve Job Description Information API
Job descriptions play a very important role in work management on the i5. Whenever an interactive, batch, autostart, or prestart job begins its life, a number of significant job attributes are retrieved from the job description assigned to that job. As a consequence, job descriptions need to be carefully created, managed, and assigned, or unexpected and serious problems might be the outcome.
As programmers, we can help avoid these problems by writing utilities that make the system administrator's job easier. To write utilities that work with job descriptions, understanding the Retrieve Job Description Information (QWDRJOBD) API is helpful.
For example, if a library contained in the initial library list of a job description is deleted, and that job description is assigned to a user profile, the user profile in question can no longer sign on to the system but instead receives the error message "CPF1113 Library in initial library list not found." To help avoid such situations, and provide a working example of the QWDRJOBD API, I've written the Work with Referenced Job Descriptions (WRKREFJOBD) command.
The WRKREFJOBD lets you find and list all job descriptions referenced by specific objects on your system, before deleting or renaming these objects. From the resulting list panel, you then have various options to perform against the selected job descriptions, such as change, display, or delete the job description.
The WRKREFJOBD utility also provides yet another opportunity to demonstrate how powerful it is to combine list and retrieve APIs. In this example, I use the Open List of Objects (QGYOLOBJ) API to create a list of job descriptions and subsequently, for each returned qualified job description name, I use the QWDRJOBD API to retrieve the attributes of the job description. Using this information, I then evaluate the specified selection criteria and decide whether the job description should be included in the Work with list panel.
You can specify all combinations of job description and library special values to narrow your search to specific or generic job descriptions in various libraries. To list all job descriptions in library QSYS that have a user profile specified, run the following command:
WRKREFJOBD JOBD(QSYS/*ALL) USRPRF(*ANY)
You also have an option to define the relationship between multiple selection criteria. If you specify an OR relationship, all job descriptions meeting just one of the criteria are included in the list. Specifying an AND relationship includes only the job descriptions meeting all the specified criteria.
The preceding command leads to the display of the following list panel (optionally, you can have the list printed instead):
The list panel offers three alternate views, all in all displaying each job description's user, print device, initial library list flag, partial request data, job and output queue as well as text description. Cursor-sensitive help text is provided for the list panel and the command to explain all details.
In case you are wondering how job descriptions are assigned to the different job types that I've mentioned, here's a brief overview:
Interactive jobs pick up the job description from the work station entry that they are signing on through. By the default special value *USRPRF, the work station entry points to the signing-on user profile's job description, as you can see on the partial command prompt of the Add Workstation Entry (ADDWSE) command:
Add Work Station Entry (ADDWSE)
Type choices, press Enter.
Subsystem description . . Name
Library . . . . . . . . *LIBL Name, *LIBL, *CURLIB
Work station name . . . . Name, generic*
Work station type . . . . *ALL, 3179, 3180, 3196...
Job description . . . . . *USRPRF Name, *USRPRF, *SBSD
Library . . . . . . . . Name, *LIBL, *CURLIB
To inspect how your system is set up, run the Display Subsystem Description (DSPSBSD) command against your interactive subsystem and select option 4 (Work station name entries) and 5 (Work station type entries). From each resulting panel, you can specify option 5 for the entry that you want to display.
Batch jobs rely on the Submit Job (SBMJOB) command's job description (JOBD) parameter to locate the job description under which the job should run. By default, this parameter also points to the special value *USRPRF. In this context, *USRPRF refers to the user profile specified on the SBMJOB command's user (USER) parameter, which by the default value *CURRENT points to the user profile running the SBMJOB command. So if the default values are used for these two parameters, the submitting user profile is the origin of the job description for the submitted batch job.
For prestart jobs and autostart jobs, the job description is named directly on the Add Prestart Job Entry (ADDPJE) and Add Autostart Job Entry (ADDAJE) commands, respectively. In both cases, special values can also be used to define the job description parameter. Diving deeper into i5/OS work management is beyond this article's scope, but I have collected a number of links providing useful information for learning more about this topic:
This APIs by Example includes the following sources:
CBX161 -- Work with Referenced Job Descriptions - CCP
CBX161E -- Work with Referenced Job Descriptions - UIM Exit
CBX161H -- Work with Referenced Job Descriptions - Help
CBX161P -- Work with Referenced Job Descriptions - Panel Group
CBX161X -- Work with Referenced Job Descriptions
CBX161M -- Work with Referenced Job Descriptions - Build Command
To create all these objects, compile and run CBX161M. Compilation instructions are in the source headers, as usual.
Q: I'm new to ILE, and I'm not sure whether I bound my program to a *MODULE or to a *SRVPGM. How can I find out how it was bound to verify that I did it correctly?
A: Whenever you're new to something, it's always good to have a way to check the results of what you did, to ensure that it all worked the way you expected it to. ILE is no different.
The Display Program (DSPPGM) command tells you which modules were copied into your program, as well as which service programs your program references. For example:
DSPPGM PGM(mylib/mypgm)
The first screen is helpful for checking that your program has the correct activation group, as well as adopted authority settings, and so forth. It looks like this:
These are the modules that you listed in the MODULE keyword of the Create Program (CRTPGM) command, or that were included as *MODULE type entries in a binding directory. If you used a binding directory, only the modules actually used are listed here.
If you like, you can key the number 5 next to each module to view more information about it. This option tells you the source file and member from which the module was created, the date and time that source member was last modified, and lots of other stuff.
When you're done viewing the details of the module(s), you're returned to the screen where it listed them. If you hit the Enter key once again, you see the service programs referenced. Here's what that screen looks like:
Display Program Information
Display 4 of 7
Program . . . . . . . : MYPGM Library . . . . . . . : MYLIB
Owner . . . . . . . . : GOODGUYS
Program attribute . . : RPGLE
Detail . . . . . . . . : *SRVPGM
Type options, press Enter.
5=Display
Service
Opt Program Library Signature
QRNXIE QSYS D8D9D5E7C9C540404040404040404040
QRNXUTIL QSYS D8D9D5E7E4E3C9D34040404040404040
QLEAWI QSYS 44F70FABA08585397BDF0CF195F82EC1
UTILR4 *LIBL E4E3C9D3D9F4E2E3C1E3C9C340404040
Bottom
F3=Exit F4=Prompt F11=Display character signature F12=Cancel F17=Top
F18=Bottom
The first three service programs listed (the ones in library QSYS) are automatically bound to all ILE RPG programs. I didn't have to specify them in a binding directory or on the CRTPGM statement. They're always included automatically because they contain routines that the RPG runtime environment needs to run an RPG program.
The last one (UTILR4) is one of my own service programs. Because I found it here in the service programs section and not on the modules screen, I know that I'm calling the service program's routines instead of calling its modules directly. That's important, because I don't want to have to rebind all my programs if I make a change to the UTILR4 service program.
The DSPPGM command makes verifying that you created your *PGM object with the right parameters easy. When you want to check a *SRVPGM object to see which modules or other service programs it references, you can use the Display Service Program (DSPSRVPGM) command. It works almost exactly the same as DSPPGM, except that it shows the details of a *SRVPGM object instead of a *PGM object.
When you want to control how long journal receivers are available online, you will want to "age" the receivers. For example, if you want to keep five days' worth of transactions online, you can either manually delete the old receivers or run the command presented this month.
The Remove Journal Receivers (RMVJRNRCV) command lets you age the receivers and optionally connect the journal to a new receiver.
You can run this command against all your journals, including QAUDJRN, to perform an intelligent deletion of old receivers.
The command performs a clean-up process against the specified journal's receiver directory. You can specify the number of journal receivers to retain, the number of days (since detachment), or a combination of both. The force parameter controls whether the journal receivers should be saved to be eligible for deletion and, for remote journals, whether replication should occur. Optionally, you can have the CHGJRN command run to change the journal receiver (before directory clean up). I've also included the Sequence option so you can ensure that the journal entry numbering is continued, regardless of the current default value of the CHGJRN command.
For more details about command parameters and command usage, refer to the help panel group.
The following source code is included. As always, check the source code headers for compile instructions and additional documentation.
Journals are used by i5/OS for many purposes, such as
recording before and after images of database record inserts, changes, and deletions
recording security-related events like authority failures, invalid sign-ons, changes to system values, and deletion of objects
recording user-defined events
If you are curious about how many journals exist on your system, you can run the command WRKJRN *ALL/*ALL. There are a multitude of journals; most are used for recording database changes. IBM supplies many of the journals, and others are user created. One of the issues that you run into with journals is that the associated journal receivers can often require significant disk space. The journal receiver is actually the storage area for the data collected through the journal.
How Big Are My Journal Receivers?
To display all your current journal receivers and to get a listing of the size of each receiver, you can use the following command:
Or you can choose the OUTFILE option and place the output in a database file. You can then use a query tool to list the receiver name (ODOBNM), the library name (ODLBNM), and the receiver size in bytes (ODOBSZ). I think if you add up the size of all your journal receivers, you'll be surprised at the amount of disk space used to hold all journaled data. Some of you will be appalled.
You will want to delete the journal receivers that are no longer needed. To identify those that are not needed, look at the detach date and whether the receiver has been saved. You determine how many days of receiver data you need by considering your requirements for reporting, freeing disk space, and forensic research on the receiver data.
How to Call an API Without Worrying About the 64 KB Limit
Q: I want to return information about the indices built over a physical file. I'm using the Retrieve Member Description (QUSRMBRD) API to do that. One of our files has more than 70 logical files built over it, and each index needs 2,176 bytes of space. If my math is correct, that means I need 217,600 bytes! Is there a good way of overcoming RPG's 64 KB limitation? In the future, I want to be ready to handle even more indices, should they be needed!
A: The QUSRMBRD API, like many APIs, can tell you how much space it needs to return all its results. Rather than use a variable whose size must be known at compile-time, I suggest using dynamic memory allocation. That way, you can ask the API how much space it needs and then tell the operating system that you need exactly that much memory. Using this technique, you won't have to worry about RPG's 64 KB limitation.
The QUSRMBRD API when called with format MBRD0400 returns an array of information about the indices of a file. At the start of MBRD0400, there's information about how much space the API needs to return a complete array. The start of the format looks like this:
D MBRD0400 ds qualified
D based(p_RcvVar)
D BytesRtn 10I 0
D BytesAvail 10I 0
D Count 10I 0
D Offset 10I 0
As you can see, I based this data structure on a pointer. I ask the system to allocate enough memory to store this minimal data structure, and I pass that to the API. Of course, the API can't fit any indices in the preceding data structure, but it fills in the BytesAvail variable, and that variable tells me how much memory I need if I want the whole thing.
The first time through the loop, nothing has yet been allocated to p_RcvVar, so it is set to *NULL. When this happens, I use the %ALLOC built-in function (BIF) to ask the operating system for enough memory for the minimal data structure.
The second time through the loop, I release the memory that the operating system previously provided, and I ask for enough memory to get everything the API has to offer and call the API again.
In almost all cases, the program runs through the loop only twice. If someone manages to add a new index between the time I call %ALLOC and the time I call the API, I end up looping a third time to expand the memory again. In the end, though, I get the whole thing.
After you do that, you can use the same pointer logic that you typically use with offsets provided by APIs to loop through the returned data and do something with it. For example:
D InxDS DS Based(p_InxDS)
D qualified
D LibNam 258A Varying
D FilNam 258A Varying
D MbrNam 258A Varying
D CstTyp 11A
D 9A
D InxVld 1A
D InxHld 1A
D 6A
D CrtDTM 14A
D RBldDTM 14A
D UseDTM 14A
D SttDTM 14A
D UseCnt 20I 0
D SttCnt 20I 0
D Stt2Cnt 20I 0
D Keys 20I 0
D Size 20I 0
D Key1Unq 20I 0
D Key2Unq 20I 0
D Key3Unq 20I 0
D Key4Unq 20I 0
D RBldSec 10I 0
D DlyKeys 10I 0
D OvFlCnt 10I 0
D CdeSiz 10I 0
D LFRdRqs 20I 0
D PFRdRqs 20I 0
D 56A
D Sparse 1A
D DrvKey 1A
D Partnd 1A
D Maint 1A
D Recvry 1A
D Type 1A
D Unique 1A
D SrtSeq 1A
D SrtLib 10A
D SrtNam 10A
D SrtLang 3A
D SrtWgt 1A
D PagSiz 10I 0
D KeyLen 10I 0
D KeyCnt 10I 0
D 82A
D KeyLst 1024A Varying
.
.
for ix = 1 to MBRD0400.Count;
p_InxDS = p_RcvVar + MBRD0400.Offset +
(ix-1) * %size(InxDS);
// do something with the data in the InxDS
// data structure here.
endfor;
dealloc p_RcvVar;
Don't forget to use the DEALLOC opcode at the end of the program to return the allocated memory to the system. (If you forget, it won't be released until the activation group ends.)
Q: I'm writing an RPG program that has a numeric field defined as "5P 2". If it contains a value such as 3.00, I want to move it to a character field and display it without trailing zeroes. However, if the decimal has a value such as 3.01, I want to display it as is. How can I do this?
A: There isn't an edit code or edit word that strips trailing zeroes, so you have to write a bit of program logic.
The basic premise of this code is to check the variable to see whether anything is in the decimal places. If it is, move them to the numeric field. If not, strip them before you move them. The following code works in V5R1 or later:
D mynum s 5 2
D char s 7A
.
.
if %dec(mynum:3:0) = mynum;
char = %char(%dec(mynum:3:0));
else;
char = %char(mynum);
endif;
The %DEC() built-in function (BIF) is used to convert the number to a 3,0. In other words, it's the same field, but without the two decimal places. If it still has the same value after the decimal places have been stripped, you know that those decimal places were zero. Therefore, you can format this 3,0 field into the character variable.
If stripping the decimal places causes the value to be different, then a value must've been in those decimal places, so they're kept in when formatting the string
Q: I need to write a CL program that processes the contents of an IFS directory. I need it to allow wildcards, both that exist and that don't exist. For example, I want to process *.csv in the directory. For all files that end in csv (and match the pattern), I want to copy them to database files and move them to a different place in the IFS. For those that don't end in csv, I want to move them to a different place in the IFS. The wildcard pattern can be different on each call to my program. How can I do that in CL?
A: In the May 19, 2005, issue of this newsletter, I provided some CL commands that you can use to read the contents of an IFS directory. After these commands are installed on your system, you can read a directory as easily from CL as you can from any other language.
In this article, I enhance those commands to meet your needs. I provide the source code for the CL commands and a service program that enables the same support from RPG, and I demonstrate how to use them.
The original article from the May 19, 2005, issue of this newsletter let you pass a regular expression when opening a directory. A regular expression is a pattern-matching scheme, similar to the wildcard in your example but more powerful.
The problem with the original article is that it provides only the file names that match the regular expression, and not those that don't match. I've extended the utility by adding parameters to "reverse" the regular expression. When you tell the utility to reverse the regular expression, instead of returning only the files that match, the utility returns only those that don't match.
For example, here's a CL program that uses my commands to read all the files in the /data/dir directory that end in CSV:
For the sake of demonstration, I use the SNDPGMMSG command to print the file name of each file as a *DIAG message that you can view in your job log.
Notice that I provide a regular expression of CSV$ to the OPENDIR command. The $ character means that the pattern is matched only at the end of each file name, so this finds all files that end in CSV. The regular expression matching that OPENDIR uses is case-insensitive, so this matches files that end in CSV, Csv, csv, and any other combination of upper case and lower case that you can think of.
There's also a REWINDDIR command in my utility. When you run that command, it moves back to the start of the directory list and lets you read it again. This new revision of the utility adds a REVERSE parameter to the REWINDDIR command. If you tell it to reverse, when you read the directory again, it gives you the files that don't match the regular expression instead of the ones that do.
Here's another example. This time, I read the directory list twice: The first time, I retrieve all the files that end in CSV, and the second time, I retrieve all those that do not:
In the preceding sample, the code first reads every file that matches the pattern CSV$, just as the previous example did. After it has finished reading through the directory the first time, it uses the REWINDDIR command (in red) to tell the system to read the directory again. This time, however, it specifies REVERSE(*YES), which tells it to retrieve the opposite files (i.e., those that do NOT match CSV$), so I get everything that doesn't end in CSV.
In blue, I put the code that prints the file names to the job log. In your program, you need to replace this blue code with the code that moves your files to the appropriate directories. In the first section, you process CSV files, so you need to move your files to the correct directory for CSV processing and copy them to database files. In the second section, they are non-CSV files, so you need to move them to the alternate directory.
Under the covers, these commands work by calling subprocedures in an ILE RPG service program. If you're not an RPG programmer, rest assured that after you compile this utility on your system, you won't need to know anything about the RPG code. You can use the CL command wrappers that I demonstrated earlier from your ILE and OPM CL programs.
However, if you need the same sort of support from RPG, you might find calling the subprocedures directly handy. For example, here's a program written in RPG and very similar to the preceding CL programs:
H DFTACTGRP(*NO) BNDDIR('IFSDIR')
/copy ifsdir_h
D d s 10I 0
D file s 640A
D msg s 52A
/free
*inlr = *on;
d = IFS_opendir('/data/dir': 'CSV$');
if (d < 0);
msg = IFS_error();
dsply msg;
return;
endif;
dsply ' ** ending in CSV **';
dow (IFS_readdir(d: file) > 0);
msg = file;
dsply msg;
enddo;
dsply ' ** not ending in CSV **';
IFS_rewinddir(d: *ON);
dow (IFS_readdir(d: file) > 0);
msg = file;
dsply msg;
enddo;
IFS_closedir(d);
return;
/end-free
To build the utility, I provide a CL program called BUILD that's also included in the code download. There's also a readme.txt file that contains further instructions about how to build the utility.
The CL commands in this utility are based on the ones that I demonstrated in the May 19, 2005, issue of this newsletter. You can read that article at the following link: http://www.iseriesnetwork.com/article.cfm?id=50930
The RPG code in this utility is based on code demonstrated in the May 12, 2005, issue of this newsletter. You can read that article at the following link: http://www.iseriesnetwork.com/article.cfm?id=50900