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.
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.
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:
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.
VSIO_VSAM_RC
VSIO_VSAM_FUNCTION
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.
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 assembler source for the routine - VSAMIO,
the JCL to assemble the above source - which is not required to be run as the routine is already assembled into SYSC.LINKLIB,
2 COBOL copybooks for the interface blocks that are used to facilitate communication between MVT COBOL programs and the assembler routine,
COBOL source for 17 test/example programs,
22 jobstreams of JCL to execute the test/example programs, as well as create and delete the test data,
6 jobstreams that use IDCAMS to display information about or print the test datasets.
The dataset SYSC.VSAMIOP.SOURCE contains:
the assembler source for the "wrapper" routine that allows PL/1 programs to call the base VSAMIO routine - VSAMIOP,
the JCL to assemble the above source - which is not required to be run as the routine is already assembled into SYSC.LINKLIB,
2 PL/1 %include copybooks for the interface blocks that are used to facilitate communication between MVT PL/1 programs and the assembler routine,
PL/1 source for 15 test/example programs,
20 jobstreams of JCL to execute the test/example programs, as well as create and delete the test data,
6 jobstreams that use IDCAMS to display information about or print the test datasets.
The dataset SYSC.VSAMIOP.MACLIB contains:
2 PL/1 %include copybooks for the interface blocks with a blocking factor compatible with the MVT PL/1 compiler.
The VSAM I/O routine - VSAMIO - and the "wrapper" routine - VSAMIOP - are already assembled and linked into SYSC.LINKLIB.
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 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.
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.
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.