VSAM File Access for PL/1
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/1 caller. The instructions on this page are for installing the intermediary program, the PL/1 "copybooks", and the sample PL/1 programs. You will need to have already installed the VSAMIO program as a prerequisite. If you haven't already install the base VSAMIO, here is what you will need to do to accomplish that:
I have not repeated all the development background here that is included on the page: VSAM File Access for COBOL, but if you follow the four steps above, you may continue following the instructions below and you will have everything you need to use the routine with PL/1 programs.
Your PL/1 program calls the intermediary program, VSAMIOP. There are three variables passed by address - two structures containing control variables and an Input/Output buffer. VSAMIOP extracts the addresses of the actual variables from the descriptor blocks that PL/1 constructs and calls VSAMIO using these addresses. VSAMIO does all the processing on the VSAM dataset and control is returned, first to VSAMIOP and then to the calling PL/1 program. The intermediary program, VSAMIOP, is only used to facilitate communication with VSAMIO. So, the remainder of the documentation on this page will refer to VSAMIO, even though the PL/1 CALL is in fact to VSAMIOP.
The single routine, VSAMIO, will handle all access to one or more VSAM datasets for the calling program. Prior to calling the routine to open each VSAM dataset to be processed, a parameter block is populated to customize the characteristics of the dataset and the manner in which it will be accessed. Therefore, it is not necessary to know Assembler or how to use the Assembler VSAM control block macros. In fact, the routine is assembled during installation into a library and will only need to be linked to the PL/1 object module during linkage editing. It is not necessary to reassemble the routine each time a PL/1 calling program is compiled.
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:
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:
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.
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:
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:
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:
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.
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 Routine
Everything you need to install the routine is in the archive: vsiopinst.tgz [MD5: 2F87A24C16B7873BF18B3B546D10E28F] which contains a single MVS jobstream in the file VSIOINST.JCL. Download and uncompress this archive with the command:
(on Linux) or use WinZip or ZipNAll on Windows/??.
The VSIOINST.JCL jobstream consists of five steps -
The names of the three Partitioned Datasets which will be created and catalogued are currently set in the jobstream as:
You may change these names as you prefer. You most definitely will need to change the VOL=SER and probably the UNIT= for the two datasets. They are currently set to UNIT=3380 and VOL=SER=MVS801.
Submit the jobstream to MVS. You should expect some error messages from the first step, since the datasets being deleted should not exist on your system. However, I use the SET AMS command to set the return code to zero following the deletes. You must receive zero return codes from the second, third, and fourth steps. The fifth step, the link edit, will receive a return code of 0008. This is because VSAMIOP calls VSAMIO and that produces an unresolved reference until both routines are link edited with a calling PL/1 program.
VSAMIO and VSAMIOP are not reentrant. Since the MVT PL/1 compiler cannot compile dynamic calls, the routines must be statically linked into every load module. Therefore, I did not make the effort to make it reentrant.
Component Cross Reference
With the addition of VSAMIOP and a second set of example programs, it can be confusing about what dataset contains what component and where it came from. So, the table below lists all of the datasets created by my installation jobstreams and their contents:
|VSTEST01.JCL||n/a||Creates a sequential dataset of 100 instream card images used in subsequent jobstreams. DSN=SYS2.VSAMTEST.DATA, UNIT=3350, VOL=SER=PUB001|
|VSTESTE1.JCL||n/a||Uses IDCAMS to delete and then define an empty Entry Sequenced cluster. DSN=VSTESTES.CLUSTER, VOL=SER=MVS803, suballocated out of existing space|
|VSTESTE2.JCL||ESDSLOAD||Reads images from non-VSAM dataset and writes them into VSAM Entry Sequenced cluster.|
|VSTESTE3.JCL||ESDSREAD||Reads images from VSAM Entry Sequenced cluster and prints them.|
|VSTESTE4.JCL||ESDSUPDT||Reads images sequentially from VSAM Entry Sequenced cluster and selectively updates records.|
|VSTESTE5.JCL||ESDSADDT||Reads images from SYSIN and appends to VSAM Entry Sequenced cluster.|
|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|
|VSTESTR2.JCL||RRDSLODS||Reads images from non-VSAM dataset and writes them into VSAM Numbered cluster, generating sequential relative record numbers ranging from 1 through 100.|
|VSTESTR3.JCL||RRDSREAD||Reads images from VSAM Numbered cluster and prints them.|
|VSTESTR4.JCL||RRDSLODR||Reads 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.)|
|VSTESTR5.JCL||RRDSUPDT||Reads images sequentially from VSAM Numbered cluster and selectively updates and deletes records.|
|VSTESTR6.JCL||RRDSRAND||Randomly updates VSAM Numbered cluster - adds, updates, and deletes images, using data from SYSIN.|
|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.|
|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|
|VSTESTK2.JCL||KSDSLOAD||Reads images from non-VSAM dataset and writes them into VSAM Indexed cluster.|
|VSTESTK3.JCL||KSDSREAD||Reads images from VSAM Indexed cluster and prints them.|
|VSTESTK4.JCL||KSDSUPDT||Reads images sequentially from VSAM Indexed cluster and selectively updates and deletes records.|
|VSTESTK5.JCL||KSDSRAND||Randomly updates VSAM Indexed cluster - adds, updates, and deletes images, using data from SYSIN.|
|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.|
|LISTCATE.JCL||n/a||Uses IDCAMS to list catalog entry for Entry Sequenced cluster: VSTESTES.CLUSTER.|
|LISTCATR.JCL||n/a||Uses IDCAMS to list catalog entry for Numbered cluster: VSTESTRR.CLUSTER.|
|LISTCATK.JCL||n/a||Uses IDCAMS to list catalog entry for Indexed cluster: VSTESTKS.CLUSTER.|
|PRINTE.JCL||n/a||Uses IDCAMS to print contents for Entry Sequenced cluster: VSTESTES.CLUSTER.|
|PRINTR.JCL||n/a||Uses IDCAMS to print contents for Numbered cluster: VSTESTRR.CLUSTER.|
|PRINTK.JCL||n/a||Uses IDCAMS to print contents for Indexed cluster: VSTESTKS.CLUSTER.|
|VSTEST99.JCL||n/a||Uses IDCAMS to delete all test datasets (Non-VSAM and VSAM) created in this test suite.|
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.
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.