MVS TOOLS AND TRICKS OF THE TRADE May 1991 Sam Golob MVS Systems Programmer sbgolob@cbttape.org MEMBER GROUP SELECTION IN PARTITIONED DATASETS. It's well known that I'm a big fan of the "PDS" program product that's on File 182 of the CBT MVS Public Tape. (The CBT tape can be ordered through NaSPA). I use "PDS" (or its vendor-supported successor called "PDS/E Sysprog Utilities") 95 percent of the time in my work. Some people (who evidently haven't exploited these products) had suggested that I've "overemphasized PDS" in my writings. Well, the fact is that I haven't written about "PDS" here for close to a year, in deference to those people. But any product that has 1000 separate utility functions (more in the case of PDS/E), and integrates the functioning of your ISPF sessions, deserves more press and not less, in my opinion. This month, I'll show you one of the most powerful tools you can use to manipulate partitioned datasets and their members. It happens to be a little-known part of both "PDS" products. Since the free "PDS" product and the vendor-supported "PDS/E" product have much of this material in common, we'll often refer to both products collectively as PDS(/E). We'll start off discussing an annoying problem that surfaced at my last shop. The situation is typical of an exposure which can happen in all production environments. Similar things also cropped up years ago, when I was a DOS applications programmer. There is something here for everyone to learn about. What's not so obvious, is that you can do an action to SOLVE such a problem conclusively in very short order. Our company had a "bad subroutine" linked into dozens of production load modules. We'll call the routine TIMDATE for the discussion here (the real routine is proprietary). There were so many occurrences of this routine buried in our production libraries that we thought it impossible to root them out immediately. But until they were all replaced with good versions in all load modules, we'd be in constant danger of middle-of-the-night phone calls, and of production CICS regions crashing "at random" in the middle of the day. This situation actually happened last January 1st. TIMDATE, this in-house "time-date routine", was coded to take a user abend when the year was incorrect. The TIMDATE module was a part of some CICS transactions and was also a part of many batch load modules. In a CICS transaction, its user abend would crash the entire CICS region, because CICS operates under one TCB. In a batch job, its user abend would force a night call from operations to the duty programmer, who'd have to re-linkedit the offending load module. That was a rather unpleasant occurrence for all involved, since very often, the duty programmer did not have a "dial-up" at home. To re-linkedit the module, that programmer would have to drive to work in the wee hours of the morning. Using two features of the "PDS" product, I cured the entire problem in two hours flat, before any lasting damage could be done to corporate production. This technique is little-known, even among many seasoned systems people. I myself didn't know about that particular feature of "PDS(/E)" before we had the problem. Hence I was moved to write something about it, to help everybody benefit in the future. HOW TO CURE THE CASE OF A "BAD" SUBROUTINE. My "magic tools" to cure the problem of the bad subroutine were: the under-utilized "IF" subcommand of the "PDS" program product, and the little-known "RELINK" operand of the PDS "MAP" subcommand. Let's look at "IF" first. I think that a great part of IF's lack of exploitation is caused by the fact that it's hard to figure out what a command called "IF" is supposed to do. "If" is a rather nondescript word in the English language. As a command, the word "if" is completely meaningless. "If" does suggest something that's conditional. But what? What would a vague conditional command in a product that manipulates datasets be useful for? I'll tell you what "IF" really does. The IF subcommand of PDS actually grabs members of a library according to some criterion, so they can belong to a "member group". Any subsequent "PDS" utility operation could be made to operate on ALL the members in that group, and on no other members of the library. People just aren't used to having a utility that will CONDITIONALLY form groups of pds members which you want to manipulate together. Other products don't generally do that sort of thing. That's why I think this powerful "IF" facility is not widely understood. This "IF" feature of PDS(/E) is what saved our skins. "IF" can pick out members of a library according to more than fifty separate selection conditions. Here's how IF works. You start with a large member group, perhaps ALL MEMBERS (denoted by the colon ":") Then you use one of the fifty-odd criteria to cut the size of the group down, allowing in the new group, only those members of the starting group that fit the condition specified. What criteria can you choose from? See Figure One for a list of what's available in the "PDS" products. One of these criteria is called MODULE (modname). When applied to a library of load modules, MODULE (modname) will pick load module members according to whether the csect called "modname" was linkedited into that load module. Voila. We have what we need. With this command, we can quickly locate all load modules in our production load libraries (and only those modules), that contain the subroutine called TIMDATE. We just have to point the "PDS" program to our load library, enter the command: "IF : MODULE(TIMDATE) THEN (SUBLIST)" to ask for a correct list of members, and let the computer crank away. When the computer's done, we have our member list. Finding the bad load modules is two-thirds of the problem. The rest? How do we fix the problem for each load module we've found? That's where the RELINK operand in the "MAP" subcommand of PDS enters. Given a load module or a group of load modules, the MAP command with the RELINK keyword will generate complete JCL and linkage editor control statements to correctly re-linkedit the load module. See Figure Two. Doing that for one load module is an amazing feat. But with PDS(/E), a product that supports operations on groups of members, this "linkedit JCL generation" can be done for all load modules in the group using one command. Fact. In the "PDS" product, we do an operation to a group of members by substituting an asterisk "*" instead of the member name, in any PDS subcommand. For example, suppose we've adjusted our load library member group with "IF : MODULE(TIMDATE)", to contain only load modules having the csect called TIMDATE linkedited in them. Then we can generate complete re-linkedit JCL for all of these modules at once. We simply issue the one command: "MAP * RELINK". Again, see Figure Two, which shows a result of these actions. The JCL and linkage editor control statements generated by "MAP * RELINK" can be put out to a dataset and then edited. We can save these generated control statements by issuing the command, "CONTROL DSN(dataset.name)", where "dataset.name" is the name of an available dataset which will be written to. After the "CONTROL DSN" command has been issued, all PDS commands and outputs are written out to that dataset as a file. To close the file, the "CONTROL NODSN" command must be issued, and the output file will be closed by the PDS program. A quick editing job on the dataset, will create a job to replace the bad subroutine with a good one of the same name. Slap a job card on top of the stream, and submit the linkedit job. This will completely cure our dangerous situation. See Figure Three. So, to solve the case of the bad subroutine, the necessary sequence of PDS(/E) commands reads as follows. PDS load.library (points "PDS" to a library) IF : MODULE(TIMDATE) THEN(SUBLIST) (creates a member subgroup of all modules containing TIMDATE) CONTROL DSN(output.dataset.name) (opens an output dataset to be written to) MAP * RELINK (generates re-linkedit JCL and writes it to the output dataset) CONTROL NODSN (closes the output dataset) The dataset 'output.dataset.name' is then edited to create a working linkedit job. After manually inserting some linkedit INCLUDE statements to replace module TIMDATE with a "good" version, and after changing SYSLMOD to point to a new output load library, the job is ready to run. When the job has finished, that library has been completely "cured" from containing the bad subroutine. We repeat this procedure for all load libraries suspected of containing load modules with the bad subroutine, and our problem is solved. SOME OTHER USES OF THE "IF" SUBCOMMAND OF "PDS(/E)" Figure One, which lists criteria by which the "IF" subcommand chooses members, is worth study. Space has unfortunately prevented the inclusion of the rest of this HELP text for "IF", which explains the significance of each criterion in detail. If you have access to a CBT Tape, or you have installed PDS or PDS/E, stydy these detailed explanations in the "PDSHELP" member of File 182, for ideas about this subcommand's power. For now, we'll say a few more general things that'll give you an glimpse. "IF" criteria can be broken into several categories. For source modules, ISPF stat criteria are major factors. Picking by userids or by dates are examples. To use "IF" to pick members by record count, ISPF statistics are NOT used. Actual record counts are. The ABOVE and BELOW keywords would do that picking job. I find that in a source-type dataset, date-picking is helpful. I can pick a group of all members I've used in the past two weeks only. These will help me look at work that I've done most recently, without interference from the older members. You get the idea. The process is aided by the fact that the MEMLIST (or "ML") subcommand, which picks members for inclusion in PDS's ISPF member list, shares most of the criteria of "member picking" with the "IF" subcommand. I get into a PDS "ISPMODE" session. I enter, "ML : BIWEEK", and I'm in business. For load modules, there is far more choice. Every load module attribute that appears in the partitioned dataset directory can be used as a criterion. Also, alias criteria, loadability, and blocksize (largest block) are criteria that are available when needed. Even TTR range within a library is in bounds for picking groups of members. If you think about all this, there is an enormous range of system problems that can be solved, using this facility. The "THEN" and "ELSE" keywords, which determine the consequences of an "IF" member search, should be explained. One can use "THEN" to specify a PDS(/E) subcommand which will operate on the member group produced from the IF member search. One can use "ELSE" to force that member group to include only members NOT satisfying the criteria of the search. It is recommended to use "SUBLIST" as the resulting operand of the THEN (or ELSE) keywords most of the time. That's because non-default cases of the other subcommands cannot be specified when they are operands of "THEN". If "SUBLIST" is used, the result of the member search is merely a member group consisting of a list of specific members. Referring to this list later as the member group, using asterisk "*", you can afterward let any subcommand operate on this member group, allowing all options that are available for the subcommand. A concluding example should drive this concept home. Suppose we have a library of load modules, some of which have blocks bigger than the DCB blocksize of the load library, and we want to fix the problem of the large-block members. The following sequence of commands will do the trick. First, we get into the load library with the command, "PDS 'our.load.library'". Then we use "IF" to nail the correct member subgroup. We say, "IF : BLOCKERR THEN (SUBLIST)". This will make a member group consisting of all members having blocks bigger than the DCB blocksize. Then we allocate a new load library that has a blocksize large enough to accommodate these members. The PDS "VERIFY *" command will provide us with an allowable blocksize. We then say, "COPY * new.library", which copies all the "large-block" members to the new library, that has room to accommodate the large blocks. Then we say "DELETE *" to delete these members from the current library. After that, we can compress the library with the PDS "COMPRESS" subcommand. COMPRESS preserves the current member group as a list of member names. Finally, we can "CHANGE 'new.library'" which makes the current dataset that PDS is using, the new library. The "CHANGE" subcommand does not alter the current PDS member subgroup. Finally, a command of "COPY * 'old.load.library' will cause PDS to create an IEBCOPY "COPYMOD" command, to reblock the "big" members, as they are being copied back to the old load library. This solves our problem. Note that the "IF" subcommand with the "THEN(SUBLIST)" keyword, determined the default member group that was used throughout all our other maneuvers. The "IF" subcommand, you see, was the heart of the matter. "IF" picked the appropriate group of library members for us to operate on. Space is short here. This subject is far deeper than I have indicated. I hope these remarks inspire you to take another look at some of the power that is buried in the PDS(/E) member selection subcommands, such as "IF" and "MEMLIST". A little time so invested, is guaranteed to yield surprising and pleasing results for you and your shop. Good luck. See you next month. * * * * * * * * * * * * * * * * * * * * * * * FIGURE ONE. Part of the TSO HELP member for the "IF" subcommand of PDS. This illustration hints at many of the techniques made possible through having so many member choice criteria. Detailed explanations for each criterion are part of the actual TSO HELP member as shipped from CBT Tape File 182 (member PDSHELP). Space constraints have made it impossible to show all of those explanations here. =IF=I )F EXAMPLE - IF MEMA:MEMB CHANGED(11/24/90:11/30/90) THEN(EDIT) THE IF SUBCOMMAND SEARCHES FOR MEMBERS MEETING DEFINED CONDITIONS. THE ACTION TO BE TAKEN IS SPECIFIED BY THE THEN AND ELSE KEYWORDS. EITHER THE THEN OR ELSE KEYWORD MAY BE OMITTED; IF BOTH THEN AND ELSE ARE OMITTED, A DEFAULT OF THEN(ATTRIB) IS PROVIDED. IF ALL CONDITIONS ARE MET FOR A GIVEN MEMBER, ANY "THEN" ACTION IS TAKEN FOR THAT MEMBER; OTHERWISE, ANY "ELSE" ACTION IS TAKEN. ALIASES - I, IF DEFAULTS - MEMBER, THEN(ATTRIB) IF NEITHER THEN NOR ELSE IS ENTERED REQUIRED - NONE )X SYNTAX - IF MEMBER SINCE/BEFORE TODAY/YESTERDAY/WEEK/CURRENT/BIWEEK/ MONTH/QUARTER/HALFYEAR/YEAR/BIYEAR/ LAST(NUMDAYS)/DATE(MM/DD/YY) CHANGED(YYDDD:YYDDD) / CHANGED(MM/DD/YY:MM/DD/YY) CREATED(YYDDD:YYDDD) / CREATED(MM/DD/YY:MM/DD/YY) ABOVE(COUNT1) (refers to record count) ALIAS/NOALIAS (is the member an alias?) AMODE24/AMODE31/AMODEANY/NOAMODE24/ NOAMODE31/NOAMODEANY (LOAD MODULES ONLY) APFERR/NOAPFERR (LOAD MODULES ONLY) APPARENTALIAS/NOAPPARENTALIAS AUTH/NOAUTH (LOAD MODULES ONLY) BELOW(COUNT2) (refers to record count) BLOCKERR/NOBLOCKERR (LRECL too long for DCB?) DC/NODC (LOAD MODULES ONLY) EDIT/NOEDIT (LOAD MODULES ONLY) EXEC/NOEXEC (LOAD MODULES ONLY) EXTERN/WKEXTERN (LOAD MODULES ONLY) FLEVEL/NOFLEVEL (LOAD MODULES ONLY) HASALIAS/NOHASALIAS (are there alias members?) ID(PUID)/NOID/NOTID(PUID) (choose by ISPF userids) IOERR/NOIOERR LKED(PARTL) (LOAD MODULES ONLY) LKEDERR/NOLKEDERR (LOAD MODULES ONLY) LOADERR/NOLOADERR (LOAD MODULES ONLY) LOADONLY/NOLOADONLY (LOAD MODULES ONLY) LRECLERR/NOLRECLERR (LRECL and BLKSIZE wrong) MAXBLK(SIZE) (pick members having big blocks) MODULE(PARTM) (LOAD MODULES ONLY) NAMEERR/NONAMEERR (members with invalid/valid names) NULL/NONULL (null/non-null members) ORPHAN/NOORPHAN OVERLAY/NOOVERLAY (LOAD MODULES ONLY) PAGE/NOPAGE (LOAD MODULES ONLY) REFR/NOREFR (LOAD MODULES ONLY) RENT/NORENT (LOAD MODULES ONLY) REUS/NOREUS (LOAD MODULES ONLY) RLDERR/NORLDERR (LOAD MODULES ONLY) RLDZERO/NORLDZERO (LOAD MODULES ONLY) RMODE24/RMODEANY/ NORMODE24/NORMODEANY (LOAD MODULES ONLY) SCTR/NOSCTR (LOAD MODULES ONLY) SPFEDIT/NOSPFEDIT (is somebody else editing member?) SSI(HEXDATA)/SSI/NOSSI/PARTSSI(HEXDATA) SYSMOD(PARTU) / USERDATA(PARTU) (LOAD MODULES ONLY) TEST/NOTEST (LOAD MODULES ONLY) TRANS(PARTT) (LOAD MODULES ONLY) TTR(LTTR:HTTR) (where in the dataset is the data?) USERDATA(PARTU) / SYSMOD(PARTU) (LOAD MODULES ONLY) USERID(PUID)/NOUSERID/NOTUSERID(PUID) (ISPF userid?) VSLKED/NOVSLKED (LOAD MODULES ONLY) ZAP(PARTZ) (exist zaps?) (LOAD MODULES ONLY) THEN(ATTRIB/ BROWSE/ DIRENTRY/ DELETE/ EDIT/ END/ FIND/ FSE/ HISTORY/ LIST/ MAP/ MEMBERS/ MEMLIST/ OUTCOPY/ PRINTOFF/ REVIEW/ SUBMIT/ SUBLIST/ TSOEDIT/ TSOLIST/ VERIFY) ELSE(ATTRIB/ BROWSE/ DIRENTRY/ DELETE/ EDIT/ END/ FIND/ FSE/ HISTORY/ LIST/ MAP/ MEMBERS/ MEMLIST/ OUTCOPY/ PRINTOFF/ REVIEW/ SUBMIT/ SUBLIST/ TSOEDIT/ TSOLIST/ VERIFY) * * * * * * * * * * * * * * * * * * * * * * * FIGURE TWO. Here is an illustration of the power of the PDS(/E) "MAP" command with RELINK option, pointed at a load module called "LOOK". This is the output of the MAP with RELINK in its raw form (as it comes directly from PDS or PDS/E). *** LOG TABLE OUTPUT *** 3.50.27 PM TUESDAY MAR 12, 1991 - DSN=JRC1.JRCSBG.LOAD,VOL=SER=JRCV02 MEM=LOOK >map look relink ** MAP LOOK //LKED EXEC PGM=IEWL, // PARM='NCAL,MAP,LIST,LET' //SYSUT1 DD UNIT=SYSDA,SPACE=(2048,(200,20)) //SYSPRINT DD SYSOUT=* //SYSLIB DD DISP=SHR,DSN=JRC1.JRCSBG.LOAD //SYSLMOD DD DISP=SHR,DSN=JRC1.JRCSBG.LOAD //SYSLIN DD * INCLUDE SYSLIB(LOOK) ORDER LOOK,CBMACS,CBBKLS,DUMMY SETSSI CB325264 MODE RMODE(ANY),AMODE(31) SETCODE AC(1) ENTRY LOOK NAME LOOK(R) * * * * * * * * * * * * * * * * * * * * * * * FIGURE THREE. Output of a "MAP * RELINK" PDS(/E) Command, after a JOB card has been added, and extraneous message information has been removed. This job is ready to run. In fact, it is part of the actual job I ran, that inspired this article (although the names have been changed to protect the innocent). //TSTBRLNK JOB (TS,2322),'TECH.SUPP-SAM.GOLOB',CLASS=M,NOTIFY=TSTBSSG, 00010000 // MSGLEVEL=(1,1),MSGCLASS=T TYPRUN=HOLD 00020000 //* 00030000 //LKED EXEC PGM=IEWL, // PARM='NCAL,MAP,LIST,LET' //SYSUT1 DD UNIT=SYSDA,SPACE=(2048,(200,20)) //SYSPRINT DD SYSOUT=* //SYSLIB DD DISP=SHR,DSN=SYS1.ONL.LINKLIB //SYSLMOD DD DISP=SHR,DSN=TSTBSSG.CICLINK.LOAD //SYSLIN DD * INCLUDE SYSLIB(TIMDATE) INCLUDE SYSLIB(AJ0004) ORDER AJ0004,TIMDATE,PICALLPM,ILBOABN,ILBODSP,ILBODTE,ILBOEXT ORDER ILBOQIO,ILBOSRV,ILBOSYN,ILBOVOC,ILBOBEG,ILBOCHN,ILBOCKP ORDER ILBOCMM,ILBOCOM0,ILBOINT,ILBOMSG,ILBOVIO ENTRY AJ0004 NAME AJ0004(R) //LKED EXEC PGM=IEWL, // PARM='NCAL,MAP,LIST,LET' //SYSUT1 DD UNIT=SYSDA,SPACE=(2048,(200,20)) //SYSPRINT DD SYSOUT=* //SYSLIB DD DISP=SHR,DSN=SYS1.ONL.LINKLIB //SYSLMOD DD DISP=SHR,DSN=TSTBSSG.CICLINK.LOAD //SYSLIN DD * INCLUDE SYSLIB(TIMDATE) INCLUDE SYSLIB(AJ0016) ORDER AJ0016,TIMDATE,ILBODSP,ILBODTE,ILBOEXT,ILBOQIO,ILBOSRV ORDER ILBOVOC,ILBOABN,ILBOBEG,ILBOCHN,ILBOCKP,ILBOCMM,ILBOCOM0 ORDER ILBOINT,ILBOMSG,ILBOVIO,ILBOWTB ENTRY AJ0016 NAME AJ0016(R) /* //