Don Stanley Sysware Consulting Group Box 634 Wellington NEW ZEALAND
NB: This paper was originally written in 1992 and may need upgrading for current versions of MVS. Some of the information in this document may be out of date. We would appreciate any update information being passed to us to upgrade this document. A particular point of interest here is that a number of JFCB dates are 2 digit as at the writing of this paper and the paper may require updating to reflect any Y2K compliancy.
Member Name (PDS)
Volume Serial No
Logical Record Length
Generation Data Group
UNBUFFERED Infile Option
EOV Infile Option
Using JFCB With FILE Statement
Date Member Created
Last Modified Date
Time Member Last Changed
Current Size of PDS Member
Last Changing Userid
There are a number of areas of SAS which the manuals do not expand upon at all. Some of these are where SAS have given access to system control blocks. The information given by SAS is insufficient to access these blocks, however a considerable amount of useful information exists in them.
This paper is primarily concerned with the JFCB (Job File Control Block) option of the data step commands INFILE and FILE, and with the DIRDD (directory of ddname) option of PROC SOURCE.
The IBM manual GC28-4010-2 contains several pages of information about how to use JFCB. However it is slanted towards assembler programmers who can do much more with JFCB then SAS users.
The manual GC26-1038-2 evidently also contains information about JFCB. However I have not seen a copy of this, so I cannot comment on its usefulness.
The other place where information is available is the library SYS1.MACLIB, member IEFJFCBN, on your system. This gives a breakdown of the layout of JFCB for assembler and PL/1 programmers. Much useful information is in here, but if you don't have some assembler or PL/1 experience, its very difficult to find your way around. This paper summarises much of what is found in that member from a SAS perspective.
For DIRDD I have found no useful information about PDS directories. I am sure that this exists somewhere, but just finding it in the maze of IBM manuals is difficult.
For JFCB, 10 elements which are of interest are discussed. These are
It is assumed in each of the following headings that a statement of the form
INPILE ddname JFCB=JFCB
has already been issued in a datastep and may require EOV or UNBUFFERED options as well.
In the JFCB, these items are found as follows.
The first through 44th bytes of the JFCB. Dataset name can be obtained by the SAS statement
dsname = substr(jfcb,1,44)
Note that from Release 6.08 the PATHNAME function returns similar information that JFCB returns in the Dataset Name.
The 8 bytes starting at byte 45 of the JFCB. If the dataset is not a pds, these are blank. Member name can be extracted by the statement
member = substr(jfcb,45,8)
This is the type of dataset. It is derived from the bit structure of the 99th byte as follows. If the first bit is on, the dataset is indexed sequential (IS). The second bit on is physical sequential (PS), also known as sequential. Bit three on is direct access (DA) which is the type of access used for V5 SAS datasets. Bit seven is on if the dataset is partitioned (PO), also known as a PDS.Remaining bits refer to rarely used organizations and are not discussed here.(NB for Don -- FInd FS and Add on)
The SAS code to detect the type of organization is as follows.
byte99 = substr(jfcb,99,1)
if byte99 eq '1.......'b then dsorg='IS' ;
else if byte99 eq '.1......'b then dsorg='PS' ;
else if byte99 eq '..l.....'b then dsorg='DA' ;
else if byte99 eq '......l.'b then dsorg='PO';
else dsorg = 'OT';
Note the use of the period in the bit test. We are only interested in the on bit, thus a period to reflect the don't care status is used. In fact, for this case, those bits should always be off.
This reflects whether the dataset is catalogued or not. The first bit of byte 53 is on if the dataset is catalogued.
The SAS code is
byte53 = substr(jfcb,53,1) ;
if byte53 eq '1.......'b then catlg = 'C';
else catlg = 'U';
This is the volume which the dataset resides on. In fact since datasets may reside on several volumes, JFCB has room for up to 5, plus a pointer to other volumes. The pointer information, should it be
needed, can be found in SYS1.MACLIB(IEFJFCBN), the variable of interest is JFCBEXAD. It is not presented here as it is rare for a dataset to extend over more than 5 volumes.
To extract the VOLSERS, use the 30 bytes starting at byte 119. Each block of 6 bytes is a VOLSER. Although it is rare to use more than one, all 5 are presented here as follows.
volserl = substr(jfcb,119,6) ;
volser2 = substr(jfcb,125,6) ;
volser3 = substr(jfcb,131,6) ;
volser4 = substr(jfcb,137,6) ;
volser5 = substr(jfcb,143,6) ;
This is stored in bytes 105 and 106. The relevant SAS code is
lrecl = input(substr(jfcb,105,2),ib2.)
The dataset block size is stored in bytes 102 to 104. The SAS code is
blksize = input(substr(jfcb,102,3),ib3.)
This is determined by the bit structure of byte 101. The easiest way to present it is using the SAS code as follows.
bytel0l = substr(jfcb,101,1)
if bytel0l eq '11......'b then recfm= 'U ' ;
else if bytel0l eq '10.0.00.'b then recfm= 'F ' ;
else if bytel0l eq '01.0.00.'b then recfm= 'V ' ;
else if bytel0l eq '10.1.00.'b then recfm= 'FB ' ;
else if bytel0l eq '01.1.00.'b then recfm= 'VB ' ;
else if bytel0l eq '10.1.10.'b then recfm= 'FBA' ;
else if bytel0l eq '01.1.10.'b then recfm= 'VBA' ;
else if bytelol eq '10.1.01.'b then recfm= 'FBM' ;
else if bytel0l eq '01.1.0l.'b then recfm= 'VBM' ;
Bit 7 of byte 87 is on if a member of a gdg. The SAS code is
byte87 = substr(jfcb,87,1) ;
if byte87='......l.'b then gdg='Y' ;
else gdg=' ';
Thus the variable gdg returns Y(es) if the dataset is a member of a gdg.
When a dataset exists for the duration of a job as a scratch dataset, eg by coding DSN=&&dsname in the JCL, the dataset is temporary. This is as opposed to permanent when a dataset is catalogued (usually).
Temporary datasets have bit 8 of byte 88 on. To determine a temporary dataset use the following SAS code.
byte88 = substr(jfcb,88,1);
if byte88 eq '.......l'b then keepstat='T' ;
else keepstat='P' ;
This refers to the DISP option in JCL. There is a subtlety here relating to existing datasets. A dataset has bit pattern 0l in bits one and two if it already exists, whether the dataset is opened for OLD or SHR. If the dataset is SHR, the 5th bit is also on.
The SAS code for detecting DISP is
byte88 = substr(jfcb,88,1) ;
if byte88 = 'll......'b then disp = 'NEW' ;
else if byte88 = '10......'b then disp = 'MOD' ;
else if byte88 = '0l......'b then disp = 'OLD' ;
else if byte88 = '0l..l...'b then disp = 'SHR' ;
This is stored in bytes 81 to 83. Byte 81 contains the year of creation, bytes 82 and 83 are the day in the year, ie the date is JULIAN
The SAS code is
byte81 = substr(jfcb,81,1) ;
creatl = put(input(byte8l,ibl.),$2.) ;
byte82 = substr(jfcb,82,2) ;
creat2 = put(input(byte82,ib2.),$3.) ;
created = put(datejul(input(creatl || creat2,5.)),date7.);
Update:: The byte at position 80 is the century. It is 0 for 20th century dates and 01 for 21st century dates. This should not be necessary to use provided YEARCUTOFF is set appropriately.
There are two INFILE options which can be very useful with JFCB. These are UNBUFFERED and EOV.
This option causes SAS to not look ahead when reading the input file. only the current line is read.The option is useful when accessing JFCB for partitioned data sets as it saves having to specify a member name. By default if you attempt to open a pds in SAS and omit the member name, you get a system error. UNBUFFERED prevents this happening. Issuing the SAS statement
INFILE <ddname> UNBUFFERED JFCB=JFCB;
causes the JFCB information to be loaded, but for a pds will cause an abend if an INPUT is attempted later (as you don't specify a member to read).
Note that if you use UNBUFFERED, reading from the dataset will still occur as if UNBUFFERED was not present, except for a pds. However the I/O times will be considerably increased. There are situations where the option is useful other than for JFCB reading of PDS datasets, but they are rare.
EOV is necessary when reading concatenated datasets as it sets a flag to indicate that a new dataset is being read. Thus it is an indication that the JFCB information has changed.
Issue an INFILE like
INFILE <ddname> JFCB=JFCB EOV=EOV;
Then EOV is set to 1 when the concatenation switch occurs.At this point the JFCB information should be reaccessed.
Use with the FILE statement is identical to that of INFILE, except JFCB information can only be obtained about files opened for output.
In general, you don't need to use the option with FILE as INFILE is safer (you don't open anything for OUTPUT), and virtually anything you want from JFCB can be obtained from INFILE.
Of course there is a useful exception. Sometimes it is desirable to know the JES job number for the current job. One way to get this is to use JFCB with SYSOUT files having a dd of the form:
//SYSOUT DD SYSOUT=*
and the following macro to obtain the job number.
%macro jesno ;
%global sysjesno ;
data null ;
file sysout jfcb=jfcb mod ;
dsname = substr(jfcb,1,44);
if dsname ne: 'JES2' then
call symput('sysjesno','UNKNOWN') ;
else do ;
jesno = substr(scan(dsname,2),4) ;
call symput('sysjesno',jesno) ;
%mend jesno ;
This works because SYSOUT datasets allocated by JES have the form JES2.xxx0yyyy.SYS.... where the yyyy is the jes job number.
DIRDD allows pds directory processing. Note that PROC SOURCE only works on a pds, and cannot handle concatenated data sets.
The pds directory is essentially a map of the members of the pds. As well as detailing the physical location of members, it also keeps track of last changing userid, date of creation and change, time of change, as well as information on current and initial size. In fact,, if you use the ISPF option to browse a pds, all the information available on-screen before member selection is available from DIRDD.
The following information available from DIRDD is discussed here:
PROC SOURCE indd=<fileref> dirdd=<filename> ; run ;where <filename> is defined in JCL as a flat 80 byte file, eg::
//FILENAME DD DSN=&&TEMP,SPACE=(CYLS,(1,1))),RECFM=FB,LRECL=80The following pieces of SAS code assume that the data has been read back in from the PROC SOURCE output dataset. Thus an input statement of the form
INPUT @l LINE $CHAR80.has already been issued.
This is in the first 8 bytes from SOURCE. Use the following SAS code to extract the data.
member = substr(line,1,8);
This information is available from bytes 18 to 20. Byte 18 contains the year of change, stored as pkl. Byte 19 and the first half-byte of byte 20 contain the day of change, stored in JULIAN form. The
final halfbyte of byte 20 appears to usually be '1111'b or '0000'b. To pick up the date this final half-byte needs to be stripped off.
The code to read the creation date is
year = put(input(substr(line,18,1),pkl.),$2.) ;
day = compress(put(substr(line,19,2),$hex3.)) ;
if day ne '000' then created=put(datejul(input(year || day,5.)),date7.);
The year is read as packed decimal, then converted to a character value. Thus for a year of 89, the character variable YEAR has a value of '89'.
The day is more complex, due to the need to strip off the final half- byte. That is accomplished by taking the 2 byte character value and applying a format of $HEX3. That format converts the character value to a three byte hex representation.
When converting from character to hex, each character byte will occupy two bytes in the hex representation. Thus a two byte character variable requires $HEX4 to convert it completely to hex. Applying $HEX3. has the effect of stripping off the last half-byte.
The variable day contains the number of days since the start of the year. The final step is to convert the JULIAN date into a character variable of the form ddMONyy.
ALTERNATIVE – I have not tested this but I believe that the 3 bytes between 18 and 20 can be read as PK3. and the DATEJUL function used to convert to SAS dates. Also the byte at position 17 is a century flag, ‘00’x means 20th century, ‘01’x means 21st century.
The logic and code are virtually identical to that for CREATION DATE above. The only difference is that the data exists in bytes 22-24.
The code is
year = put(input(substr(line,22,1),pkl.),$2.) ;
day = compress(put(substr(line,23,2),$hex3.)) ;
if day ne '000' then changed=put(datejul(input(year || day,5.)),date7.);
ALTERNATIVE – I have not tested this but I believe that the 3 bytes between 22 and 24 can be read as PK3. and the DATEJUL function used to convert to SAS dates. Also the byte at position 21 is a century flag, ‘00’x means 20th century, ‘01’x means 21st century.
This is stored in bytes 25 and 26. The code to access this is as follows.
day = compress(input(substr(line,25,1),pkl.)) ;
time = put(day,$2.) || ':' ;
day = compress(input(substr(line,26,1),pkl.)) ;
time = compress(time) || put(day,$2.) ;
if time eq: '0:0 ' then time = ' ' ;
Note that the time is returned as a character variable of length 5 in the form hh:mm, where the hour ranges from 00 to 23, and the minute from 00 to 59.
This is found in bytes 27 and 28. The SAS code to extract the current member size is
currsize = input(substr(line,27,2),ib2.)
This is measured in records, i.e. the number of records in the member. To get the size in bytes, obtain the PDS logical record length from JFCB and multiply the two.
This is the userid which last modified the pds member. It is found in bytes 33 to 42. The SAS code is
changeid = substr(line,33,10)