VSAM File Access for PL/I

 

Revised May, 2020: Following the revision of my MVS 3.8j installation instructions/tutorial, I received some feedback that the instructions for installing the VSAM file access routines for COBOL and PL/1 were hard to follow and/or there were portions that simply did not work as described.  When I created the SYSCPK volume, containing the various language compilers and other tools, I included the VSAM I/O routines, both COBOL and PL/1, in SYSC.LINKLIB, as well as the source code in their own libraries.  Therefore, following these instructions became redundant.  All that is required is that you download the SYSCPK volume and integrate it into your MVS 3.8j installation.  If you have followed my MVS 3.8j installation instructions (revision done in April, 2020), SYSCPK is already integrated into your system.  You may run the test/example programs from the datasets on SYSCPK:  SYSC.VSAMIO.SOURCE and SYSC.VSAMIOP.SOURCE.  To compile programs you write using the VSAM I/O routines, include SYSC.VSAMIO.SOURCE in the SYSLIB DD for MVT COBOL compiles or SYSC.VSAMIOP.MACLIB in the SYSLIB DD for MVT PL/1 compiles, and include SYSC.LINKLIB concatenated to the SYSLIB DD for the Link Editor.  I have modified these instructions to remove steps referencing installation of the VSAM I/O routines themselves, as they are already installed on SYSCPK.  I have also added output listings for the various test/example programs below.

Revised 7 July 2020: John James wrote to inform me he had found an error that has been in the VSAMIOFB definition since 2001 when this was originally coded.  In the area immediately following the fields that define the characteristics of the VSAM object, there is a VSFB_RESERVED field.  In this area the VSAMIO assembler code builds the access control blocks required to manipulate the object (open, close, read, start, write, rewrite).  As originally distributed, I had defined this field as 153 bytes, but the area used by the access control blocks is actually 161 bytes.  As long as the programs written using the VSAMIO routine were coded exactly as I have them coded there was no problem, but if you moved the placement of the block of storage around, as JJ did, you can encounter S0C1 and/or S0C4 abends.  I have corrected the copy members and reassembled the program, although it was not actually necessary to reassemble VSAMIO as the fault was in the copy libraries used in PL/I programs calling VSAMIO.


I had already been working my way through a PL/I textbook for a while when I decided to write my VSAMIO routine to allow programs compiled with the MVT COBOL compiler to access VSAM datasets.  It was a natural progression to want to have access to VSAM datasets from PL/I programs as well.  I wanted to be able to use the same program, but there was a problem to overcome.  PL/I passes character and structure variables by constructing a descriptor block for the variable; then the address of that descriptor block is passed to the called program instead of the address of the actual variable.  This PL/I compiler is so antiquated that it doesn't recognize any options that would override this behavior so that the called program would receive the raw variable address.

I decided that the simplest solution was to write an intermediary program that would extract the addresses of the actual variables needed by VSAMIO, call VSAMIO using those addresses as parameters, and then return control to the PL/I caller.  The instructions on this page are for installing the intermediary program, the PL/1 "copybooks", and the sample PL/1 programs.

 

Parameter Interface Blocks

Communication between the PL/1 calling program and VSAMIO is accomplished by the use of two parameter blocks.  The first block contains only the command to process against a particular dataset and return code information regarding the outcome of processing that command.  The second block contains Information regarding the VSAM dataset against which the command is to be processed.  The first parameter block is contained in the PL/1 copybook VSAMIO:

 /**********************************************************************
                                                                        
         VV   VV    SSSSS       A      M     M    IIII      OOOOO       
         VV   VV   SS   SS     AAA     MM   MM     II      OO   OO      
         VV   VV   SS         AA AA    MMM MMM     II      OO   OO      
         VV   VV    SSSSS    AA   AA   MMMMMMM     II      OO   OO      
         VV   VV        SS   AA   AA   MM M MM     II      OO   OO      
          VV VV    SS   SS   AAAAAAA   MM   MM     II      OO   OO      
           VVV     SS   SS   AA   AA   MM   MM     II      OO   OO      
            V       SSSSS    AA   AA   MM   MM    IIII      OOOOO       
                                                                        
  **********************************************************************
   THESE PARAMETERS ARE USED TO INTERFACE WITH THE VSAM DATASET ACCESS  
   ROUTINE.                                                             
                                                                        
   THE VSIO_PARAMETER_VALUES SUPPLY THE VALUES USED TO MOVE INTO        
   PARAMETER ENTRIES TO TAILOR THE ROUTINE TO A SPECIFIC DATASET AND    
   TO PROVIDE COMMANDS TO DRIVE THE ROUTINE.                            
  *********************************************************************/
                                                                        
          DECLARE                                                       
             1 VSIO_PARAMETER_VALUES   STATIC,                          
                2 VSIO_OPEN            CHAR(8)   INIT('OPEN    '),      
                2 VSIO_CLOSE           CHAR(8)   INIT('CLOSE   '),      
                2 VSIO_READ            CHAR(8)   INIT('READ    '),      
                2 VSIO_WRITE           CHAR(8)   INIT('WRITE   '),      
                2 VSIO_REWRITE         CHAR(8)   INIT('REWRITE '),      
                2 VSIO_DELETE          CHAR(8)   INIT('DELETE  '),      
                2 VSIO_START_EQUAL     CHAR(8)   INIT('STARTEQ '),      
                2 VSIO_START_NOTLESS   CHAR(8)   INIT('STARTGE '),      
                2 VSIO_KSDS            CHAR(4)   INIT('KSDS'),          
                2 VSIO_ESDS            CHAR(4)   INIT('ESDS'),          
                2 VSIO_RRDS            CHAR(4)   INIT('RRDS'),          
                2 VSIO_SEQUENTIAL      CHAR(10)  INIT('SEQUENTIAL'),    
                2 VSIO_DIRECT          CHAR(10)  INIT('DIRECT    '),    
                2 VSIO_DYNAMIC         CHAR(10)  INIT('DYNAMIC   '),    
                2 VSIO_INPUT           CHAR(6)   INIT('INPUT '),        
                2 VSIO_OUTPUT          CHAR(6)   INIT('OUTPUT'),        
                2 VSIO_INPUT_OUTPUT    CHAR(6)   INIT('UPDATE'),        
                2 (VSIO_RC_SUCCESS               INIT(0),               
                   VSIO_RC_LOGIC_ERROR           INIT(8),               
                   VSIO_RC_END_OF_FILE           INIT(9999),            
                   VSIO_RC_UNKNOWN_COMMAND       INIT(20),              
                   VSIO_RC_DATASET_ALREADY_OPEN  INIT(21),              
                   VSIO_RC_DATASET_NOT_OPEN      INIT(22),              
                   VSIO_RC_ORGANIZATION_UNKNOWN  INIT(23),              
                   VSIO_RC_ACCESS_UNKNOWN        INIT(24),              
                   VSIO_RC_ORG_ACCESS_MISMATCH   INIT(25),              
                   VSIO_RC_MODE_UNKNOWN          INIT(26),              
                   VSIO_RC_MODE_UNSUPPORTED      INIT(27),              
                   VSIO_RC_DDNAME_BLANK          INIT(28))              
                                       FIXED BINARY(15,0),              
                2 (VSIO_FB_DUPLICATE_RECORD      INIT(8),               
                   VSIO_FB_KEY_SEQUENCE          INIT(12),              
                   VSIO_FB_RECORD_NOT_FOUND      INIT(16),              
                   VSIO_FB_NO_MORE_SPACE         INIT(28),              
                   VSIO_FB_READ_WITHOUT_START    INIT(88))              
                                       FIXED BINARY(15,0),              
 /**********************************************************************
   THE VSIO_PARAMETER_BLOCK IS THE COMMUNICATION INTERFACE TO THE       
   THE ROUTINE.                                                         
  *********************************************************************/
                                                                        
             1 VSIO_PARAMETER_BLOCK    STATIC,                          
                2 VSIO_COMMAND         CHAR(8)   INIT(' '),             
                2 (VSIO_RETURN_CODE,                                    
                   VSIO_VSAM_RC,                                        
                   VSIO_VSAM_FUNCTION,                                  
                   VSIO_VSAM_FEEDBACK) FIXED BINARY(15,0) INIT(0);      
                                                                        
 /**********************************************************************
                           END OF VSAMIO COPY BOOK                      
  *********************************************************************/

The level 2 data items under VSIO_PARAMETER_VALUES are constants that may be moved into the parameter fields to customize how the routine handles a particular dataset.  By defining the constants here in the copybook, I reduce the possibility of introducing errors that hand coding literals might cause.

The level 2 data items under the second structure, VSIO_PARAMETER_BLOCK, are the actual fields passed to the Assembler routine. 

For each VSAM dataset to be processed by the VSAMIO routine, a second parameter block must be defined.  The second parameter block is contained in the PL/1 copybook VSAMIOFB:

 /**********************************************************************
                                                                        
    VV   VV   SSSSS      A     M     M  IIII   OOOOO   FFFFFFF  BBBBBB  
    VV   VV  SS   SS    AAA    MM   MM   II   OO   OO  FF       BB   BB 
    VV   VV  SS        AA AA   MMM MMM   II   OO   OO  FF       BB   BB 
    VV   VV   SSSSS   AA   AA  MMMMMMM   II   OO   OO  FFFFF    BBBBBB  
    VV   VV       SS  AA   AA  MM M MM   II   OO   OO  FF       BB   BB 
     VV VV   SS   SS  AAAAAAA  MM   MM   II   OO   OO  FF       BB   BB 
      VVV    SS   SS  AA   AA  MM   MM   II   OO   OO  FF       BB   BB 
       V      SSSSS   AA   AA  MM   MM  IIII   OOOOO   FF       BBBBBB  
                                                                        
  **********************************************************************
    THESE PARAMETERS ARE USED TO INTERFACE WITH THE VSAM DATASET ACCESS 
    ROUTINE, AND ARE USED TO COMMUNICATE CHARACTERISTICS FOR A SINGLE   
    VSAM DATASET.                                                       
                                                                        
    WITH THE 2 EXCEPTIONS FOR RECORD LENGTH (TO ACCOMODATE VARIABLE     
    LENGTH RECORDS) AND KEY LENGTH (TO ACCOMODATE RELATIVE RECORD  
    DATASETS), THESE DATA NAMES MUST BE POPULATED PRIOR TO CALLING THE  
    ROUTINE TO OPEN THE DATASET AND MUST NOT THEN BE CHANGED UNTIL THE  
    DATASET HAS BEEN CLOSED.                                            
  *********************************************************************/
                                                                        
       DECLARE                                                          
          1 VSIO_FILE_BLOCK            STATIC,                          
             2 VSFB_DDNAME             CHAR(8)   INIT(' '),             
             2 VSFB_ORGANIZATION       CHAR(4)   INIT(' '),             
             2 VSFB_ACCESS             CHAR(10)  INIT(' '),             
             2 VSFB_MODE               CHAR(6)   INIT(' '),             
             2 (VSFB_RECORD_LENGTH,                                     
                VSFB_KEY_POSITION,                                      
                VSFB_KEY_LENGTH)       FIXED BINARY(15,0) INIT(0),      
             2 VSFB_FILE_STATUS        CHAR(1)   INIT('C'),             
             2 VSFB_RESERVED           CHAR(161);                       
                                                                        
 /**********************************************************************
                          END OF VSAMIOFB COPY BOOK                     
  *********************************************************************/

As the comment above the VSIO_FILE_BLOCK group states, with the exception of VSFB_KEY_LENGTH, only when processing a relative record dataset, and VSFB_RECORD_LENGTH, only when processing a variable length dataset, these data items must be populated prior to the call to VSAMIO to open the dataset.  They must not be modified while the dataset is open.

Note:  Utilizing VSFB_KEY_LENGTH for relative record number is actually a compromise to get around the apparent limitations of the MVT PL/1 compiler.  The VSAMIO routine actually utilizes the entire fullword encompassed by VSFB_KEY_POSITION and VSFB_KEY_LENGTH when processing a relative record dataset, but I have been unable to find a method under this PL/1 compiler to redefine these two halfword variables with a single fullword variable.  This imposes a limit of 65,535 on the relative record number.

A unique copy of this file block must be provided for each VSAM dataset to be processed simultaneously.  In addition to communicating dataset characteristics with VSAMIO, it provides storage for VSAMIO to build and maintain the VSAM Access Control Blocks used to manipulate the dataset while it is open.

The record input/output area(s) for each dataset is coded as separate structure or CHAR scalar item elsewhere in the calling PL/1 program.

 

Calling the Routine

To illustrate how to use the routine, the PL/1 fragments below are taken from the one of the suite of sample programs (all of which may be downloaded from this page).  The goal of this particular program is to sequentially load a Key Sequenced dataset.

First, the command and dataset parameter blocks must be initialized and a call is made to open the dataset:

 /**********************************************************************
   ESTABLISH PARAMETERS FOR VSAM DATASET AND CALL ROUTINE TO OPEN
  *********************************************************************/
       VSFB_DDNAME = 'KSDSF01';
       VSFB_ORGANIZATION = VSIO_KSDS;
       VSFB_ACCESS = VSIO_SEQUENTIAL;
       VSFB_MODE = VSIO_OUTPUT;
       VSFB_RECORD_LENGTH = 80;
       VSFB_KEY_POSITION = 0;
       VSFB_KEY_LENGTH = 10;
       VSIO_COMMAND = VSIO_OPEN;
       CALL VSAMIOP (VSIO_PARAMETER_BLOCK,
                     VSIO_FILE_BLOCK,
                     RECORD_IMAGE);
       IF (VSIO_RETURN_CODE ^= VSIO_RC_SUCCESS) THEN
          DO;
             CALL VSIO_ERROR;
             RETURN;
          END;

The literal value, KSDSF01, assigned to VSIO_DDNAME, identifies the name that will be used on the DD statement for the dataset.  

The organization is set to KSDS, the access method is set to SEQUENTIAL, and the access mode is set to OUTPUT, which are requirements for an initial load of an indexed VSAM dataset.

The record length is set to 80.  

The key position is the offset, relative to zero, of the key from the beginning of the data record.  When set to 0, as in this case, the key begins in the first position of the record.  The length of the key field is 10 characters.

Following any call to the routine, the return code fields should be tested to determine the success or failure of the processing of the dataset.  There are four fields used to return information:

  1. VSIO_RETURN_CODE will contain 0 if no error occurred.  If any action performed on a VSAM dataset results in an error, a value - usually 8 - is returned, and will be placed in this field.  Values in the range of 20 through 28, and 9999 are special values indicating conditions signaled by my routine.  The values from 20 through 28 indicate inconsistency or error in the parameters.  The value 9999 is set to indicate end of file was reached performing a sequential read.  If VSIO_RETURN_CODE doesn't contain 0, a value in the range of 20 through 28, or 9999, the following three fields will contain additional information about the error and originates from VSAM.

  2. VSIO_VSAM_RC

  3. VSIO_VSAM_FUNCTION

  4. VSIO_VSAM_FEEDBACK will contain the most useful information in the case of an error.  I have supplied some constant values in VSIO_PARAMETER_VALUES which may be useful in interpreting VSAMIO's return code and VSAM feedback codes.

To write a record into a VSAM dataset, the record is populated with data and a call is made to the routine with the WRITE command:

 /**********************************************************************
   CALL ROUTINE TO WRITE RECORD INTO VSAM DATASET
  *********************************************************************/
       VSIO_COMMAND = VSIO_WRITE;
       CALL VSAMIOP (VSIO_PARAMETER_BLOCK,
                     VSIO_FILE_BLOCK,
                     RECORD_IMAGE);
       IF (VSIO_RETURN_CODE ^= VSIO_RC_SUCCESS) THEN
          CALL VSIO_ERROR;
       ELSE
          RECORD_COUNTER = RECORD_COUNTER + 1;

As with the OPEN, following the call to write the record, the condition names associated with the return fields are used to handle error conditions that might arise from the WRITE.

 /**********************************************************************
   CALL ROUTINE TO CLOSE VSAM DATASET
  *********************************************************************/
       VSIO_COMMAND = VSIO_CLOSE;
       CALL VSAMIOP (VSIO_PARAMETER_BLOCK,
                     VSIO_FILE_BLOCK,
                     RECORD_IMAGE);
       IF (VSIO_RETURN_CODE ^= VSIO_RC_SUCCESS) THEN
          CALL VSIO_ERROR;

After processing of the dataset is concluded, the dataset must be closed by calling the routine with the CLOSE command.  And, as with other calls, the return information should be checked to verify a successful close has occurred.  If you fail to close a VSAM dataset prior to the end of the program, you will receive an error on any future access of the dataset until you use the IDCAMS VERIFY function to reset error flags in the catalog entry for the dataset.

 

Installing the PL/1 'Wrapper' Routine

Everything required to use the VSAM I/O routine with MVT PL/1 programs is already installed on the SYSCPK volume.

The dataset SYSC.VSAMIO.SOURCE contains:

The dataset SYSC.VSAMIOP.SOURCE contains:

The dataset SYSC.VSAMIOP.MACLIB contains:

The VSAM I/O routine - VSAMIO - and the "wrapper" routine - VSAMIOP - are already assembled and linked into SYSC.LINKLIB.

 

Executing the Test/Example Programs

I have attempted to test the functions in the routine using all possible combinations of organization, access method, and access mode with a series of PL/1 programs.  The test programs are very simple, as my knowledge of PL/1 is pretty basic at this time.  Mostly I was interested in the functionality of the Assembler routine.

The source for these PL/1 programs, as well as the jobstreams to create test datasets, execute the PL/1 example programs, and delete the test datasets, are installed in SYSC.VSAMIOP.SOURCE.  You may use them as examples to see how to set up the parameter block for the various combinations of organization, access, and open mode.

Here is a cross-reference of the jobstreams, the PL/1 test program they execute, the function of the jobstream and/or program, and, for the actual PL/1 programs, a PDF of the expected output:

Jobstream

PL/1 Program

Function

View Expected Output

 

VSTEST01.JCL n/a Creates a sequential dataset of 100 instream card images used in subsequent jobstreams.  DSN=PUB001.VSAMTEST.DATA, UNIT=SYSDA, VOL=SER=PUB001 vstestp01.pdf
VSTESTE1.JCL n/a Uses IDCAMS to delete and then define an empty Entry Sequenced cluster.  DSN=PUB001.VSTESTES.CLUSTER, VOL=SER=PUB001, suballocated out of existing space vstestpe1.pdf
VSTESTE2.JCL ESDSLOAD Reads card images from non-VSAM dataset and writes them into VSAM Entry Sequenced cluster. vstestpe2.pdf
VSTESTE3.JCL ESDSREAD Reads records sequentially from VSAM Entry Sequenced cluster and prints them. vstestpe3.pdf
VSTESTE4.JCL ESDSUPDT Reads records sequentially from VSAM Entry Sequenced cluster and selectively updates records. vstestpe4.pdf
VSTESTE5.JCL ESDSADDT Reads card images from SYSIN and appends to VSAM Entry Sequenced cluster. vstestpe5.pdf
VSTESTR1.JCL n/a Uses IDCAMS to delete and then define an empty Numbered cluster.  DSN=VSTESTRR.CLUSTER, VOL=SER=MVS803, suballocated out of existing space vstestpr1.pdf
VSTESTR2.JCL RRDSLODS Reads card images from non-VSAM dataset and writes them into VSAM Numbered cluster, generating sequential relative record numbers ranging from 1 through 100. vstestpr2.pdf
VSTESTR3.JCL RRDSREAD Reads records sequentially from VSAM Numbered cluster and prints them. vstestpr3.pdf
VSTESTR4.JCL RRDSLODR Reads card images from non-VSAM dataset and writes them into VSAM Numbered cluster, deriving relative record number from portion of data record, leaving embedded empty record slots.  (Note, you will need to rerun VSTESTR1.JCL prior to this job if you have already run VSTESTR2.JCL.)    Run VSTESTR3 again to see that slots have been left empty. vstestpr1.pdf

vstestpr4.pdf

vstestpr3.pdf

VSTESTR5.JCL RRDSUPDT Reads records sequentially from VSAM Numbered cluster and selectively updates and deletes records. vstestpr5.pdf
VSTESTR6.JCL RRDSRAND Randomly updates records in VSAM Numbered cluster - adds, updates, and deletes images, using data from SYSIN. vstestpr6.pdf
VSTESTR7.JCL RRDSSSEQ Issues START against VSAM Numbered cluster, using both Key Equal and Key Greater Than or Equal options, then reads sequentially forward from started position. vstestpr7.pdf
VSTESTK1.JCL n/a Uses IDCAMS to delete and then define an empty Indexed cluster.  DSN=VSTESTKS.CLUSTER, VOL=SER=MVS803, suballocated out of existing space vstestpk1.pdf
VSTESTK2.JCL KSDSLOAD Reads card images from non-VSAM dataset and writes them into VSAM Indexed cluster. vstestpk2.pdf
VSTESTK3.JCL KSDSREAD Reads records sequentially from VSAM Indexed cluster and prints them. vstestpk3.pdf
VSTESTK4.JCL KSDSUPDT Reads records sequentially from VSAM Indexed cluster and selectively updates and deletes records. vstestpk4.pdf
VSTESTK5.JCL KSDSRAND Randomly updates records in VSAM Indexed cluster - adds, updates, and deletes images, using data from SYSIN. vstestpk5.pdf
VSTESTK6.JCL KSDSSSEQ Issues START against VSAM Indexed cluster, using both Key Equal and Key Greater Than or Equal options, then reads sequentially forward from started position. vstestpk6.pdf
LISTCATE.JCL n/a Uses IDCAMS to list catalog entry for Entry Sequenced cluster: PUB001.VSTESTES.CLUSTER.  
LISTCATR.JCL n/a Uses IDCAMS to list catalog entry for Numbered cluster: PUB001.VSTESTRR.CLUSTER.  
LISTCATK.JCL n/a Uses IDCAMS to list catalog entry for Indexed cluster: PUB001.VSTESTKS.CLUSTER.  
PRINTE.JCL n/a Uses IDCAMS to print contents for Entry Sequenced cluster:  PUB001.VSTESTES.CLUSTER.  
PRINTR.JCL n/a Uses IDCAMS to print contents for Numbered cluster:  PUB001.VSTESTRR.CLUSTER.  
PRINTK.JCL n/a Uses IDCAMS to print contents for Indexed cluster:  PUB001.VSTESTKS.CLUSTER.  
VSTEST99.JCL n/a Uses IDCAMS to delete all test datasets (Non-VSAM and VSAM) created in this test suite. vstestp99.pdf

Prior to executing the jobstreams, verify that the UNIT= and VOL=SER= entries will match DASD allocations in your MVS 3.8j environment.  The jobstreams executing PL/1 programs invoke a compile, link-edit, and execute, so the PL/1 compiler must be installed on your system.  The test/example programs do the bare minimum of producing output.  Following any of the jobs that update/modify the VSAM datasets, you can always run the jobstream that sequentially reads/displays the records from the VSAM dataset you are updating/modifying. 

 

A Note About Reading Variable Length Datasets

Prior to issuing a READ command against a variable length dataset, the record length - VSIO_RECORD_LENGTH - should be set to the length of the largest possible record and the record area provided should be large enough to accommodate a record of this size.  After the read, VSIO_RECORD_LENGTH will contain the length of the record read into the record area.


If you need to open an newly defined (empty) cluster as Input-Output and then add (Write) records to it, you will need to prime the cluster first.  Look at Prime VSAM Cluster on my miscellaneous programs page.


If you are interested in writing PL/1 programs that utilize VSAM datasets, I hope this documentation has provided you with the information you need to install my routine and get started writing programs on your own.  If I can answer any questions about installing and/or using the routine, or if you find errors in these instructions, please don't hesitate to send them to me:

And, if you figure out how to get the MVT PL/1 compiler to redefine those two halfword scalar items with a single fullword item, I would certainly appreciate knowing how you did it.


Return to Site Home Page 


This page was last updated on July 07, 2020.