BSP - Beratung, Schulung, Projekte


Utilities uncovered

Introduction

Just imagine, you have IPLed your brand-new MVT system (or MVS, if and when it becomes available for Hercules). Now you decide to change some parameters in SYS1.PARMLIB. Of course, you want to play it safe, like

  • Wear a belt
  • Wear suspenders
  • Keep your hands in your pockets
  • With other words, before you change a crucial member (let's say LNKLST00) you will want to make a safe copy, or backup, of this member. Very simple, you say:

    You fire up explorer, click with the mouse, drag the member.... Oooops - wrong timeframe, wrong system, restarting.....

    You enter COPY LNKLST00 LNKLST01 at the MVT master console.... Ooops again, it ain't as simple as that at all

    What you really do is: You call up a little helper and tell him to do the copying for you. IBM has provided a lot of little helpers in MVT, and they are called UTILITIES. We will discuss a few of the more useful ones here.

    General Syntax Guidelines

    Utilities (aka utility programs) are invoked using standard JCL. (For a short introduction see the JCL in the N.U.D.E. document). You will neither need a JOBLIB nor a STEPLIB DD statement, as the utilities are in the standard system library. Depending on the utility program you will need certain required DD statements in your JCL

    SYSPRINT

    With the SYSPRINT DD statement you tell MVT into which output queue you want the utility to place its messages and report. Usually you would code

    //SYSPRINT DD SYSOUT=A
    

    or, if you are not interested in the messages:

    //SYSPRINT DD DUMMY
    

    SYSIN

    The SYSIN DD statement specifies the dataset that contains trhe commands you want to give to the utility program. In most cases this is an instream data set, i.e.:

    //SYSIN DD *
    

    or

    //SYSIN DD DATA
    

    If you do not want to issue any specific commands (but rely on some default action the utility will take), you code:

    //SYSIN DD DUMMY
    

    Whenever you code commands for the utility (they are called Utility Control Statements), some special rules apply:

    Utility Programs

    The following utility program will be discussed in this article (eventually)

    IEFBR14
    A Dummy Program. Does Nothing. Really. Nothing. Nada. Ne Riens. Inge Ting. Nix.
    IEBGENER
    Copies sequential data sets
    IEBCOPY
    Copies partitoned data sets
    IEHDASDR
    Doing weird things to a DASD (...aka disk)
    IEBPTPCH
    Prints (or punches) partitioned or sequential datsets or members of partitioned data sets
    IEHLIST
    Prints datasets and VTOC information
    IEHPROGM
    Maintains the catalog. Can delete and uncatalog datasets

    IEFBR14

    A program that REALLY does nothing. Zilch. Why do we need such a thing? Well, look at the following JCL:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //NEWFILE  DD  DISP=(NEW,CATLG),DSN=MY.NEW.FILE,
    //             UNIT=3330,VOL=SER=WORK01,
    //             SPACE=(CYL,(10,10,10)),DCB=SYS1.PARMLIB
    //
    

    From the NEWFILE DD statement it is clearly seen that this job tries to make a new file. Unfortunately, it will fail with a JCL error!!! Can you see the error in the JCL statement?

    It is not really obvious, but the error is in the EXEC statement! Which EXEC statement? Well, THAT exec statement, the one that has not been coded. The JCL error message is

    JOB HAS NO STEPS

    And here is where IEFBR14 comes in: A program that just doesn't do anything - but keeps the MVT JCL parser happy:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //ALLOC   EXEC PGM=IEFBR14
    //NEWFILE  DD  DISP=(NEW,CATLG),DSN=MY.NEW.FILE,
    //             UNIT=3330,VOL=SER=WORK01,
    //             SPACE=(CYL,(10,10,10)),DCB=SYS1.PARMLIB
    //
    

    IEFBR14 can also be used to get rid of a data set:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //DELETE   EXEC PGM=IEFBR14
    //GETRID    DD  DISP=(OLD,DELETE,DELETE),DSN=NOT.NEEDED.ANY.MORE
    

    This is nice, of course, but what if the file to be deleted doesn't exist? Your job will fail with

    JCL ERROR, DATASET NOT FOUND

    and none of the subsequent steps will execute. Therefore it might be better to code sometimes:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //DELETE   EXEC PGM=IEFBR14
    //GETRID    DD  DISP=(MOD,DELETE,DELETE),DSN=NOT.NEEDED.ANY.MORE,
    //              UNIT=SYSDA,SPACE=(TRK,(0))
    
    If the file exists, it will be deleted, and if it does not exist, it will be created at step beginning and deleted at step end

    By the way, do not try to delete a member of a PDS with IEFBR14 - grown men have been known to cry because of this. What would the following JCL do:

    //DONTRUN  JOB  (REALLY,DONT),'RUN THIS!!',CLASS=Z,MSGCLASS=A
    //ALLOC   EXEC PGM=IEFBR14
    //GETRID   DD  DISP=(OLD,DELETE,DELETE),DSN=VERY.IMPORTNT.FILE(OBSOLETE)
    

    Will it delete the member OBSOLETE which is in file VERY.IMPORTNT.FILE? Hm - after the job completes, the member is gone. Good. but also, the file is gone. Not so good. Remember, disposition in JCL refers to the whole dataset, not to individual members!!!

    IEBGENER

    IEBGENER is basically a copy program that copies sequential file to another sequential file. It can also be used to read a sequential file and place parts of it into different members of a partitioned dataset.

    Required DD statements

    SYSUT1

    Dataset containing the INPUT data for IEBGENER

    SYSUT2

    Dataset containing the OUTPUT data from IEBGENER

    SYSIN

    Utility control statements. Can be DUMMY. If DUMMY, the default operation is COPY from SYSUT1 to SYSUT2, no editing

    SYSPRINT

    Utility Message Data Set

    To make a backup copy of a member of a PDS, you might code:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBGENER
    //SYSPRINT DD  SYSOUT=A
    //SYSUT1   DD  DISP=SHR,DSN=SYS1.PARMLIB(LNKLST00)
    //SYSUT2   DD  DISP=SHR,DSN=SYS1.PARMLIB(LNKLST01)
    //SYSIN    DD  DUMMY
    

    Note, that you code DISP=SHR on the SYSUT2 DD statement, AND NOT DISP=NEW. DISP applies to the whole dataset, not to a member!!!. Also, do not forget the member name on the SYSUT2 DD statement!!! Can you guess what happens if you do forget? IEBGENER will gladly write the contents of the member LNKLST00 to the beginning of the dataset SYS1.PARMLIB. Unfortunately, this is the place where the directory of SYS1.PARMLIB is located, and it will be overwritten, making the whole SYS1.PARMLIB dataset contents unaccessible.

    Okay, but now you want to make a backup of library SYS1.LINKLIB. Would you want to code several hundred jobs, each copying one member at a time to a new dataset? Surely not!. This is where our next utility can shine:

    IEBCOPY

    IEBCOPY is a utility program used to copy partitioned datasets (aka libraries)

    Required DD statements

    SYSUT1

    Dataset containing the INPUT data for IEBCOPY

    SYSUT2

    Dataset containing the OUTPUT data from IEBCOPY

    SYSIN

    Utility control statements. Can be DUMMY. If DUMMY, the default operation is COPY from SYSUT1 to SYSUT2, no editing

    SYSPRINT

    Utility Message Data Set

    To make a backup copy of a partitioned dataset, you can code:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //CLEANUP EXEC PGM=IEFBR14
    //SYS2LNK  DD  DISP=(MOD,DELETE,DELETE),DSN=SYS2.LINKLIB,SPACE=(TRK,(0)),
    //             UNIT=3330,VOL=SER=WORK01
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //SYSUT1   DD  DISP=SHR,DSN=SYS1.LINKLIB
    //SYSUT2   DD  DISP=(NEW,CATLG),UNIT=3330,VOL=SER=WORK01,
    //             SPACE=(CYL,(100,10,500)),
    //             DCB=SYS1.LINKLIB,
    //             DSN=SYS2.LINKLIB
    //SYSIN    DD  DUMMY
    

    The first step deletes a preexisting SYS2.LINKLIB. In the second step all the members of SYS1.LINKLIB will we copied to a newly created SYS2.LINKLIB. Finito. Simple.

    Now, sometimes you might want to use more than one pair of datasets for copying. You could achieve this with multiple IEBCOPY steps, of course. But a more elegant way can be chosen:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //SOURCE   DD  DISP=SHR,DSN=SYS1.LINKLIB
    //TARGET   DD  DISP=SHR,DSN=SYS2.LINKLIB
    //PARMLIB  DD  DISP=SHR,DSN=SYS1.PARMLIB
    //PARMSAVE DD  DISP=SHR,DSN=SYS2.PARMLIB
    //SYSIN    DD  *
     COPY INDD=SOURCE,OUTDD=TARGET
     C I=PARMLIB,O=PARMSAVE
    

    What has changed? We added Utility Control Statements. The first one tells IEBCOPY to copy from INput DDname SOURCE to the OUTput DDname TARGET, i.e. we tell IEBCOPY to use different DDnames than the default ones, SYSUT1 and SYSUT2. The second control statement looks a bit more crypric, but actually, it isn't. Commands and keywords can be abbreviated to one character codes. The second command therefore tells IEBCOPY to copy all members from SYS1.PARMLIB to SYS2.PARMLIB.

    We can also copy several datasets into one target file. There are two possibilities, one using concatenation (see JCL in the N.U.D.E.). The other one by using a list of DDames

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB   DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMLIB   DD  DISP=SHR,DSN=SYS2.PARMLIB
    //PARMPROC  DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     COPY INDD=(PROCLIB,PARMLIB),OUTDD=PARMPROC
    

    A list of DD-names is enclosed in brackets

    Actually, I have not been telling the truth, the whole truth, and nothing but the truth. Members will only be copied when they do not already exist in the target dataset, i.e. like named members will not be overwritten. This is not a problem when the target dataset has just been created. But what if the target dataset does already exist? Then we must tell IEBCOPY explicitly that we want members to be overwritten:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMPROC DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     COPY INDD=((PROCLIB,R)),OUTDD=PARMPROC
    

    Very good. So far you know how to copy complete libraries. Obviously, you would also want to jsu copy a few member, or only one. Don't even think about wildcards, they do not exist!! The correct way to copy just a few members is

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMPROC DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     COPY INDD=PROCLIB,OUTDD=PARMPROC
     SELECT MEMBER=(ONEMEMBR)
     S M=(MEMBERA,MEMBERB,MEMBERC)
    

    with other words, you add another utility control statement where you tell IEBCOPY which members you want selected. Keywords can be abbreviated again. Also, you can copy a full library except for selected members. Look at the next example:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMPROC DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     C I=PROCLIB,O=PARMPROC
     EXCLUDE MEMBER=(ONEMEMBR)
    

    Again, existing members in the target dataset will not be overwritten. If you really want to do this, code

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMPROC DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     C I=PROCLIB,O=PARMPROC
     SELECT MEMBER=((MEMBERA,,R))
    

    Note the double brackets again. This command will take MEMBERA from SYS1.PROCLIB and will copy it to SYS1.PARMPROC, and will overwrite any existing MEMBERA in the process. You might wonder what the pair of brackets commas is all about...they indicate that a parameter has been omitted. The omitted parameter specifies the new name of a member in the target dataset:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //PARMPROC DD  DISP=SHR,DSN=SYS1.PARMPROC
    //SYSIN    DD  *
     C I=PROCLIB,O=PARMPROC
     SELECT MEMBER=((MEMBERA,MEMBERB,R))
    

    This will take MEMBERA, and will store it as MEMBERB in the target dataset, overwriting an existing MEMBERB.

    Compress

    Every so often (and more often than you like) a PDS fills up with garbage. Actually, every time you change or overwrite an existing member, this member is still part of the dataset, even if it cannot easily be accessed. When your datset fills up this way, a COMPRESS is required for garbage collection. This is done using IEBCOPY again. A compress (=garbage collection) is performed if and only if the input and the output dataset of IEBCOPY are identical:

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //PROCLIB  DD  DISP=SHR,DSN=SYS1.PROCLIB
    //SYSIN    DD  *
     COPY INDD=PROCLIB,OUTDD=PROCLIB
    

    would reclaim space in the SYS1.PROCLIB dataset. Another possibility to achieve the same result would have been

    //MYJOB   JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //BACKUP  EXEC PGM=IEBCOPY
    //SYSPRINT DD  SYSOUT=A
    //SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB
    //SYSUT2   DD  DISP=SHR,DSN=SYS1.PROCLIB
    //SYSIN    DD  DUMMY
    

    IEHLIST

    provided by Kevin Shelly

    IEHLIST is a utility program used to list information from a VTOC (Volume Table of Contents), a catalog, or a PDS (Partitioned Data Set) directory.

    Required DD statements

    anyname

    A DD reference to a disk volume that IEHLIST requires in order to read stuff on that volume. Choose any DDNAME you want. The utility doesn't care.

    SYSIN

    Utility control statements.

    SYSPRINT

    Utility message data set.

    To list a Volume Table of Contents, you can code:

    //MYJOB    JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //LISTVTOC EXEC PGM=IEHLIST
    //SYSPRINT  DD  SYSOUT=A
    //VOLDD     DD  UNIT=SYSDA,VOL=SER=DLIB01,DISP=OLD
    //SYSIN     DD  *
      LISTVTOC  FORMAT,VOL=3330=DLIB01
    /*
    //
    

    This will print a list of the datasets on that volume along with some of their attributes. There are two choices for the type of report generated. The FORMAT option is formatted for easy reading. The DUMP format shows each record of the VTOC in a dump-like format.

    To list the contents of the catalog on a particular volume, you can code:

    //MYJOB    JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //LISTCTLG EXEC PGM=IEHLIST
    //SYSPRINT  DD  SYSOUT=A
    //VOLDD     DD  UNIT=SYSDA,VOL=SER=IPL001,DISP=OLD
    //SYSIN     DD  *
      LISTCTLG VOL=3330=IPL001
    /*
    //
    

    In order to list the contents of a PDS directory, you can code:

    //MYJOB    JOB  CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1)
    //LISTPDS  EXEC PGM=IEHLIST
    //SYSPRINT  DD  SYSOUT=A
    //VOLDD     DD  UNIT=SYSDA,VOL=SER=MVTRES,DISP=OLD
    //SYSIN     DD  *
      LISTPDS  DSNAME=(SYS1.LINKLIB,SYS1.PL1LIB),VOL=3330=MVTRES,FORMAT
    /*
    //
    

    Like the LISTVTOC control card, the LISTPDS control card accepts either a FORMAT option or a DUMP option. The FORMAT option is for PDS members created by the linkage editor (load modules).

    Any number of control statements can be included in one IEHLIST step as long as all the referenced volumes are mentioned in DD statements similar to the VOLDD DD statement shown above. If the DASD volume is mountable instead of permanently mounted, use something like:

    //VOLDD  DD  UNIT=(SYSDA,,DEFER),VOL=(PRIVATE,SER=PUB001),DISP=OLD
    

    instead.

    IEHDASDR

    Sorry, didn't get around to writing this chapter yet.

    Enjoy your private hercules mainframe



    © Volker Bandke