Using ISPF edit macros to displaying the junk in a catalog

You can use IDCAMS DCOLLECT to collect SMS information about data sets on your z/OS system. This gives lots of information about a dataset, size, creation date, SMS attributes etc.

With processing you can get reports on dataset, volumes, and what is using all the space. This allows you to delete dataset which are no longer needed.

This does not help when you are trying to clean out your catalogs, and removing stuff which should not be in that catalog. For example there are usually entries in a catalog which should really be in user catalogs.

I could not find tools to help me with this. I fell back to using and ISPF edit macro to process a LISTCAT listing and extracting relevant data. It is not difficult (once you know) and it is quick and easy.

This blog post gives some examples of how you can use ISPF edit macros to process data in data sets or spool.

The output from the short Rexx exec is

TCPIP.ETC.SERVICES             1998.284 B3SYS1
SYS1.RACFDS 1999.288 B3CFG1
SYS1.IPLPARM 1999.288 B3SYS1
...
LOG.MISC 2025.107 USER04
IBMUSER.S0W1.SPFTEMP3.CNTL 2026.002 USER07
IBMUSER.S0W1.SPFLOG1.LIST 2026.013 USER04
IBMUSER.SMF 2026.013 USER07

With this I asked What is LOG.MISC 2025.107 doing in the catalog? It is there because I did not have the controls in place to stop people putting datasets into the catalog.

Instead of just displaying the information, I could have had the exec create IDCAMS statements, for example to get it recataloged, or deleted; based on creating date or other information.

Get your LISTCAT listing

I used

//IBMLISC JOB 1,MSGCLASS=H 
// EXPORT SYMLIST=(*)
// SET CAT=&SYSVER.
//S1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *,SYMBOLS=JCLONLY
LISTCAT NONVSAM CATALOG(CATALOG.&CAT..MASTER) ALL
/*
  • The // SET CAT=&SYSVER. gets a local copy of the system symbol &SYSVER. You can use the operator command D SYMBOLS to list all the system symbols defined. On my system &SYSVER is Z31B
  • In //SYSIN DD *,SYMBOLS=JCLONLY the SYMBOLS=JCLONLY says substitute variables in the following SYSIN data, and substitute from the JCL symbols. &CAT is Z31B, and so the catalog name becomes CATALOG.Z31B.MASTER. You cannot use &SYMVER directly in the SYSIN data.

Edit the listing

I used SDSF and the SE line command on the output of the LISTCAT. You get an ISPF edit session with the spool data.

Run the exec

I have a Rexx exec called LISTCATN in USER.Z31B.CLIST. I’ll describe it in sections below

Standard Rexx starting code

/* REXX */ 
/*
exec to Nonvsam records from a catalog listing
*/
ADDRESS ISPEXEC
'ISREDIT MACRO (parms) '

Use MACRO(parms) to get the parameters passed to the macro

Define parsing arguments

I define search arguments in a variable stem. This separates the data from the logic, and makes it easy to change or extend.

  data.1 = "NONVSAM 2 10" 
fcol.1 = 18
flen.1 = 48

data.2 = "CREATION 38 48 "
fcol.2 = 54
flen.2 = 8

data.3 = "VOLSER 8 15 "
fcol.3 = 27
flen.3 = 8

data.0 = 3
sortcols = "50 60"

Later there is code

  • do I = 1 to data.0 this processes each section in the stems
  • There a find data.1 which substitutes to “find NONVSAM 2 10”. This says Find the string NONVSAM in columns 2 to 10
  • If the find locates the string, the code retrieves the line. The code does a substring from fcol.1 for length flen.1 and saves the value
  • data.0 = 3 says there are three data sections.
  • sortcols = “50 60” is used at the end sort the file by the date column.

Remove uninteresting records

  "ISREDIT autosave off     " 
"ISREDIT exclude all"
"ISREDIT find NONVSAM 2 10 ALL "
"ISREDIT find CREATION ALL "
"ISREDIT find VOLSER ALL "
if rc != 0 then data.0 = 2 /* ignore the volser */
"ISREDIT delete all x "
  • “ISREDIT autosave off ” I have this as standard in ISPF edit macros, basically it says do not save the data if I press PF3.
  • “ISREDIT exclude all” –
  • “ISREDIT find NONVSAM 2 10 ALL ” find these lines
  • “ISREDIT find CREATION ALL ”
  • “ISREDIT find VOLSER ALL ”
  • If volser was not found, then listcat wasn’t specified with the right statement, so do not try to process any VOLSER records
    • if rc != 0 then data.0 = 2 /* ignore the volser */
  • “ISREDIT delete all x ” delete all the records which are still excluded leaving only the records I searched for.

Process the records

do j = 1  by 1 
string = ""
do i = 1 to data.0
"ISREDIT find "data.i
if rc <> 0 then leave
"ISREDIT (f) = LINENUM .ZCSR "
"ISREDIT (d) = LINE " f
name = substr(d,fcol.i,flen.i) /* from col and length */
string = string || " " || name
end

if rc <> 0 then leave
out.j = string
end

This code uses the data in the variable stems defined higher up. It keeps the logic separate from the search data.

  • do j = 1 by 1 iterate through the whole file until the end of file
  • string = “” preset the output string
  • do i = 1 to data.0 for the records we specified
  • “ISREDIT find “data.i find it
  • if rc <> 0 then leave if not found then leave
  • “ISREDIT (f) = LINENUM .ZCSR ” Get the line number where the find found the data.
  • “ISREDIT ( d ) = LINE ” f get the line contents – getting the line number found in the previous step
  • name = substr(d,fcol.i,flen.i) /* from col and length */ extract the field of interest from the line
  • string = string || ” ” || name build up a string of the values found
  • end
  • if rc <> 0 then leave we got a not found, so end of file,
  • out.j = string save the data in a stem for processing below
  • end

Do something with the records

You can do processing on the data, for example create JCL to delete the dataset.

In this example I delete all records from the file, and insert the saved records

  "ISREDIT exclude all" 
"ISREDIT delete all x "
do i = 1 to j -1
v = out.i
"ISREDIT LINE_after .zcsr = (v)"
end
"ISREDIT sort " sortcols
exit
  • “ISREDIT delete all “ delete all processed the lines in the file
  • do i = 1 to j -1 we have a stem of the records we processed iterate over them
  • v = out.i make a copy of the data, make it easy for ISPF. ISPF only does simple substitutions
  • “ISREDIT LINE_after .zcsr = (v)” insert after the current (last) line the value from v, which is the saved string.
  • end
  • “ISREDIT sort ” sortcols sort on the creation date
  • exit

The output

The output from this is the dataset name, the create date, and the volume it is on.

TCPIP.ETC.SERVICES             1998.284 B3SYS1
SYS1.RACFDS 1999.288 B3CFG1
SYS1.IPLPARM 1999.288 B3SYS1
...
IBMUSER.S0W1.SPFTEMP3.CNTL 2026.002 USER07
IBMUSER.S0W1.SPFLOG1.LIST 2026.013 USER04
IBMUSER.SMF 2026.013 USER07

From the data information I can see which entries were due to me – because they were all after the Jan 2025.

Different ways of processing records

Not every dataset has the same information. For example, deleting uninteresting rows

NONVSAM ------- ADCD.DYNISPF.ISPPLIB 
DATASET-OWNER-----(NULL) CREATION--------2016.236
VOLSER------------B3SYS1 DEVTYPE------X'3010200F' FSEQN------------------0
NONVSAM ------- ADCD.WLM
DATASET-OWNER-----(NULL) CREATION--------2023.010
STORAGECLASS -----SCBASE MANAGEMENTCLASS---(NULL)
DATACLASS --------(NULL) LBACKUP ---0000.000.0000
VOLSER------------B3USR1 DEVTYPE------X'3010200F' FSEQN------------------0

The second dataset ADCD.WLM has SMS information, Storage Class, Management Class, and Data Class, which are not present with the first dataset ADCD.DYNISPF.ISPPLIB.

You could process this sequentially and have logic like…

If the row starts with

  • NONVSAM – then write out the previous information, get the dataset name, and start again
  • VOLSER – then parse the volser value
  • DATASET – then parse the creation date
  • STORAGECLASS – then parse the SC and MC values
  • DATACLASS – then parse the DC value

For example

"ISREDIT     (last)  = LINENUM .ZLAST" 
do j = 1  by 1  to last 
  "ISREDIT      ( d )  = LINE   " j 
  if substr(d,2,7) = "NONVSAM" then 
  do 
      count = count + 1 
      string =  dsn cd vol sc mc dc 
      sc = "        " 
      mc = "        " 
      dc = "        " 
      vol= "      " 
      dsn= "      " 
      cd = "      " 
      out.count = string 
      say string 
      /* do the next */ 
      dsn = substr(d,18,48) 
  end 
  else 
  if substr(d,9,7) = "DATASET" then cd = substr(d,54,8) 
  else 
  if substr(d,9,6) = "VOLSER" then vol = substr(d,27,6) 
  else 
  if substr(d,9,6) = "STORAG" then 
  do 
     sc = substr(d,27,8) 
     mc = substr(d,56,8) 
  end 
  else 
  if substr(d,9,6) = "DATACL" then vol = substr(d,27,8) 
end 

This gives output like

NFS.CNTL                       2000.336 B3SYS1 
SYS1.RACFDS.BACKUP 2001.164 B3CFG1
SYS1.UADS 2003.137 B3CFG1
NETVIEW.ADCD.NTVTABS 2009.027 B3USR1 SCBASE (NULL)
SYT1.ZOS.CNTL 2012.013 B3USR1 SCBASE (NULL)
TCPIP.PROFILE.TCPIP 2016.236 B3SYS1

So not difficult at all.

Keeping people out of the master catalog.

I had written Here’s another nice mess I’ve gotten into! My master catalog is full of junk which describes what I did once I found my master catalog was full of stuff which should not be there.

I’ve now got round to finding out how to stop people from putting rubbish there in the first place!

See One minute mvs: catalogs and datasets for an introduction to master and user catalogs.

The master catalog should have some system datasets, aliases, and not much else.

An alias says for this high level qualifier (COLIN) go to the usercatalog(‘USER.COLIN.CATALOG).

A catalog is a dataset, and you can use a RACF profile to protect it, so only authorised people can update it. Typically, when you define a userid or a high level qualifier, you should also define an alias for that userid (or HLQ), pointing to a user catalog.

To keep user data out of the master catalog you need

  • one or more user catalogs – for example do you give each user their own catalog, have one per deparment, or one for all users. These catalogs are typically defined by storage administrators (or automation set up by storage administrators).
  • an alias for each userid and the name of the catalog that userid should use. These aliases are set up by people (or automation) which defines userids.
  • an alias for each dataset High Level Qualifier (HLQ) and the name of the catalog that the HLQ should use. These aliases are set up by people (or automation) which defines the high level qualifiers. An example HLQ is CEE, or DB2.

If you migrate to a system with a new master catalog (for example with zPDT or zD&T), you will need to import the usercatalogs into the master catalog, and redefine the aliases.

Import a user catalog

When I tried to import a user catalog into the master catalog, I got

 ICH408I USER(COLIN   ) GROUP(TEST    ) NAME(CCPAICE             ) 
CATALOG.Z31B.MASTER CL(DATASET ) VOL(B3SYS1)
INSUFFICIENT ACCESS AUTHORITY
FROM CATALOG.Z31B.* (G)
ACCESS INTENT(UPDATE ) ACCESS ALLOWED(READ )

so any userid importing or exporting a catalog needs update access to the catalog.

Defining and deleting an alias

Having set up RACF profiles, and given my userid COLIN only READ access to the master catalog, I found my userid could still define and delete aliases. It took a couple of days to find out why.

  • If a userid has ALTER access to CLASS(FACILITY) STGADMIN.IGG.DEFDEL.UALIAS the userid can define and delete ALIAS profiles. This overrides dataset access checks.
  • If a userid does not have ALTER access to the profile, then normal dataset checks are made.

What I learned…

  • My userid had “special”. As the documentation says The RACF SPECIAL attribute allows you to update any profile in the RACF database. This meant I could display and update any profile.
  • There is a profile class(facility) STGADMIN.IGG.DEFDEL.UALIAS which allows you to define and delete user aliases in the (master) catalog
  • If my userid had SPECIAL, or the userid was in group SYS1 I could issue the command

rlist facility STGADMIN.IGG.DEFDEL.UALIAS

and it gave

CLASS      NAME
----- ----
FACILITY STGADMIN.IGG.* (G)
LEVEL OWNER UNIVERSAL ACCESS YOUR ACCESS WARNING
----- -------- ---------------- ----------- -------
00 IBMUSER NONE ALTER NO

USER ACCESS
---- ------
SYS1 ALTER
IBMUSER ALTER

If my userid did not have special and was not in SYS1, I got

ICH13002I NOT AUTHORIZED TO LIST STGADMIN.IGG.*

When my userid was connected to the group SYS1, it got the ALTER access to the profile, and overrode the RACF profiles for the catalog data set.

Which is my master catalog?

At IPL, it reports

IEA370I MASTER CATALOG SELECTED IS CATALOG.Z31B.MASTER 

You can use the operator command D IPLINFO

SYSTEM IPLED AT 07.26.58 ON 01/02/2026                                              
RELEASE z/OS 03.01.00 LICENSE = z/OS
USED LOADCP IN SYS1.IPLPARM ON 00ADF

My load parm member, SYS1.IPLPARM(LOADCP) has

IODF     99 SYS1 
INITSQA 0000M 0008M
SYSCAT B3SYS1113CCATALOG.Z31B.MASTER
SYSPARM CP
IEASYM (00,CP)

The catalog is called CATALOG.Z31B.MASTER and is on volume B3SYS1

Does a RACF profile exist?

See What RACF profile is used for a data set?

tso listdsd dataset(‘CATALOG.Z31B.MASTER’)
tso listdsd dataset(‘CATALOG.Z31B.MASTER’) generic

Showed there was no profile defined.

Create the profile

* DELDSD  'CATALOG.Z31B.*'                                   
ADDSD 'CATALOG.Z31B.*' UACC(READ)
PERMIT 'CATALOG.Z31B.*' ID(IBMUSER ) ACCESS(CONTROL)
PERMIT 'CATALOG.Z31B.*' ID(COLIN ) ACCESS(READ )

When I tried to use the master catalog from a general userid the request failed.

DELETE TEST  ALIAS                                                                                                 
IDC3018I SECURITY VERIFICATION FAILED+
IDC3009I ** VSAM CATALOG RETURN CODE IS 56 - REASON CODE IS IGG0CLFT-6
IDC0551I ** ENTRY COLIN.TEST NOT DELETED
IDC0014I LASTCC=8

Hmm that’s strange

With userid COLIN, I could still issue commands, such as DELETE TEST ALIAS, even though I had given it only read access.
If I displayed the profile from userid COLIN it had

 INFORMATION FOR DATASET CATALOG.Z31B.* (G)

LEVEL OWNER UNIVERSAL ACCESS WARNING ERASE
----- -------- ---------------- ------- -----
00 COLIN READ NO NO
YOUR ACCESS CREATION GROUP DATASET TYPE
----------- -------------- ------------
READ SYS1 NON-VSAM

This had me confused for several hours. That’s when I found out about the presence of the STGADMIN.IGG.DEFDEL.UALIAS profile.

Summary

You want users (non system) datasets to be in a user catalog, rather than the master catalog. This makes migrating to a new master catalog much easier, You just have to import the catalogs, and redefine the aliases.

You need to set up

  • one (or more) user catalogs
  • aliases to connect the userid (and High Level Qualifiers) to a catalog
  • give authorised used alter access to class(facility) STGADMIN.IGG.DEFDEL.UALIAS to allow them to maintain aliases.
  • define a RACF profile for the master catalog and make the UACC(READ).
  • for those people who need to need to define, import or export catalogs, they need update access to the master catalog dataset.

One minute mvs: catalogs and datasets.

In days of old, when 64KB was a lot of real storage, to reference a data set you had to specify the data set name and the DASD volume the data set was on. DSN=MY.JCL,VOL=SER=USER00

After this, the idea of a catalog was developed. Just like the catalog in a library, it told you where things were stored. When you created a data set, and specified DISP=(NEW,CATLG), the data set name and volume were stored in the catalog. When you wanted to use a data set, and did not specify the volume, then the catalog was used to find the volume for the data set.

As systems grew and the number of data sets grew, the catalog grew and quickly became difficult to manage. For example if you deleted data sets, the entry was logically removed from the catalog, resulting in gaps in the catalog.

After this a multi level catalog was developed. You have one master catalog. You can have many user catalogs. You define an alias in the master catalog saying for data sets starting with a specific high level qualifier, use that user catalog.

When a userid was created, most system programmers would also create an alias pointing to a user catalog. They may define a user catalog for each user, or a user catalog could be shared by many aliases.

The catalogs are managed by the VSAM component of z/OS.

Entity naming

PDS and sequential files are referred to as datasets. VSAM provides simple database objects,

  • Relative Record ( where you say get me the n’th record)
  • Key sequence. You define a primary index, and you can an index on different columns using an ALTERNATIVE INDEX. 

VSAM uses the term cluster to what you use in you JCL or application. A cluster has a data component, and zero or more index components.

Moving systems.

I have been running on a self contained ADCD system at z/OS level 2.4. I have recently installed a self contained system at z/OS 2.5. How do I get my data sets into the new system?
You can import connect a user catalog into a new (master) catalog, and define an alias in the new master catalog pointing to the user catalog.

When I did this I could then see my COLIN.* data sets. To be able to use the data sets, I need the volumes to be attached to the z/OS system.

Useful IDCAMS commands

In batch you use the IDCAMS program (IDC = prefix for VSAM, AMS is for Access Management Services!)

If you do not specify a catalog, it defaults to the master catalog.

Create a user catalog

//IBMDF  JOB 1,MSGCLASS=H                                   
//S1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE USERCATALOG -
( NAME('A4USR1.ICFCAT') -
MEGABYTES(15 15) -
VOLUME(A4USR1) -
ICFCATALOG -
FREESPACE(10 10) -
STRNO(3 ) ) -
DATA( CONTROLINTERVALSIZE(4096) -
BUFND(4) ) -
INDEX(BUFNI(4) )
/*

Creating an alias to use the catalog

The JCL below creates two aliases in the master catalog. They both point to a catalog called A4USR1.ICFCAT (which is in the master catalog)

//IBMUSERT JOB 1,MSGCLASS=H                                     
//S1 EXEC PGM=IDCAMS,REGION=0M
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE ALIAS (NAME(BACKUP) RELATE('A4USR1.ICFCAT') )
DEFINE ALIAS (NAME(COLIN ) RELATE('A4USR1.ICFCAT') )
/*

To import an existing user catalog into a master catalog

//IBMUSERT JOB 1,MSGCLASS=H                                   
//S1 EXEC PGM=IDCAMS,REGION=0M
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
IMPORT CONNECT -
OBJECTS -
(('A4USR1.ICFCAT' VOLUME(A4USR1) DEVICETYPE(3390) -
))
/*

From the information provided, the master catalog knows the name, and location of the user catalog.

List an entry

You can list information about an entry, such as a data set, or a catalog using the LISTCAT command

LISTCAT ENT(COLIN.USERS) ALL

Listing aliases

You can use the IDCAMS command LISTCAT with alias

LISTCAT ALIAS

which gives a one line per entry list of all of the aliases

ALIAS --------- ADCDA       
ALIAS --------- ADCDB
ALIAS --------- ADCDC
ALIAS --------- ADCDD
...

LISTCAT ALIAS ALL

gives

ALIAS --------- ADCDA                                                       
...
ENCRYPTIONDATA
DATA SET ENCRYPTION-----(NO)
ASSOCIATIONS
USERCAT--USERCAT.Z24C.USER
ALIAS --------- ADCDB
...
ENCRYPTIONDATA
DATA SET ENCRYPTION-----(NO)
ASSOCIATIONS
USERCAT--USERCAT.Z24C.USER

So we can see that the the alias ADCDA maps to user catalog USERCAT.Z24C.USER

Listing a catalog

The command

LISTCAT CATALOG(USERCAT.Z24C.USER)

gives

CLUSTER ------- 00000000000000000000000000000000000000000000       
DATA ------- USERCAT.Z24C.USER
INDEX ------ USERCAT.Z24C.USER.CATINDEX
NONVSAM ------- ADCDA.S0W1.ISPF.ISPPROF
NONVSAM ------- ADCDA.S0W1.SPFLOG1.LIST
NONVSAM ------- ADCDB.S0W1.ISPF.ISPPROF
...
CLUSTER ------- SYS1.VVDS.VC4CFG1
DATA ------- SYS1.VVDS.VC4CFG1

Which shows there is a data component of the catalog called USERCAT.Z24C.USER, and there is index component called USERCAT.Z24C.USER.CATINDEX.

The catalog has a data component (USERCAT.Z24C.USER) and an index component USERCAT.Z24C.USER.CATINDEX.

Within the catalog are entries for data sets such as ADCDA.S0W1.ISPF.ISPPROF, and system (DFDSS) dataset SYS1.VVDS.VC4CFG1 - which contains information what is on the SMS DASD volume C4CFG1.