BPAM Basics
Using BPAM you can access a partitioned data set (PDS) or a
partitioned data set extended (PDSE). A partitioned data set is
similar to a physical sequential data set, however, the data is
partitioned into discrete segments called members. A portion of a
PDS's space is reserved for a directory containing information
that is used by the operating system to locate a member's
storage. A member's location is recorded in TTR format, where
"TT" is 2 bytes identifying the track and "R" is a single byte
identifying the block.
When a member is added to a PDS, an entry for the member is inserted into the directory. Entries in the directory are ordered by member name in ascending sequence. Only one user at a time can add a member to a PDS.
A member's records can be updated in place. However, the only way to extend a member is to completely rewrite it (i.e., DISP=MOD will not cause records to be added to the end of a member as it does with sequential files). The same holds true for selective deletion of records: you must re-write the member while excluding those records that you no longer want.
When a member is deleted from a partitioned data set, its entry is removed from the directory. However, the space previously occupied by the member's records cannot be reused until the data set is "compressed" by a utility such as IEBCOPY.
PDSE data sets, from a usage standpoint, are identical to partitioned data sets, but with additional capabilities. Most importantly, PDSEs permit multiple users to write to a data set simultaneously (though each user must be writing a different member). PDSEs also reuse storage that is freed by member deletions, and thus, do not require periodic compression.
When using BPAM to read a partitioned data set member or members, you must allocate the data set without specifying a member name. A special function, FINDM, is used to position to a member prior to reading it. When creating a new member, the STOWM function is used to create a directory entry after all of the member's records have been written.
Partitioned and partitioned extended data sets can reside only on
DASD. In addition, PDSEs must be managed by DFSMS.
Record Formats
The records in partitioned data sets can take several forms:
The REXXTOOLS BPAM functions support the following formats (RECFMs):
F | FA | FB | FBA | FBM | FM |
V | VA | VB | VBA | VBM | VM |
U | UA | UM |
For information regarding the selection of an appropriate data set organization and record format refer to the IBM publication Using Data Sets for your system's level of DFP or DFSMS.
Note: PDS and PDSE data sets cannot be defined with
RECFM=FS or RECFM=FBS (spanned records).
Programming For BPAM
The basic flow of a program that processes a data set using BPAM
is as follows:
Note: You can concatenate multiple partitioned data sets to a single ddname for reading only. When searching for a member using FINDM, the first occurrence of the member name in the concatenation of partitioned data sets is the one that will be located.
When writing records, the PUT function accepts the record to be written as an argument. PUT handles any blocking that may be required, and writes physical blocks to the file as required.
Shadow REXXTOOLS maintains information about open BPAM files in a data structure associated with the task under which the OPEN function was executed. The information is maintained by ddname. Files remain registered with REXXTOOLS and open until they are explicitly closed, or until the task that opened them terminates.
All REXX programs under a MVS task share the same REXXTOOLS BPAM data structures. Thus if program A calls program B (REXX CALL or function call), any ddname that is opened by either program A or program B is known to the other. File sharing also extends to directly subtasked REXX programs. As a consequence, if Program A attaches program B, the files opened by program A are known by program B. However, files opened by program B will not be known to A when control is returned. This is because task termination will close all files opened by program B.
Notes:
/* REXX */ address rexxtool "alloc fi(indd) da(user.data) shr" if open('bpam','indd','input') <> 0 then do say 'OPEN failed with RC='rc 'REASON='reason exit 8 end if findm('indd','timecard') <> 0 then do say 'TIMECARD member not found.' exit 8 end timerec = get('indd') do while rc = 0 parse var timerec lname +10 fname +10 timein +8 timeout +8 timerec = get('indd') end call close 'indd' "free fi(indd)"
/* REXX */ address rexxtool "alloc fi(outdd) da(user.data) new sp(1 1) track", "dsorg(po) recfm(v b) lrecl(255) dir(10) unit(sysda)" call open 'bpam', 'outdd', 'output' say 'Enter member name or ENTER to quit.'; parse pull member do while member <> '' say 'Enter records. Terminate with a null line.' parse pull record do while record <> '' call put 'outdd', record parse pull record end call stowm 'outdd', member say 'Enter member name or ENTER to quit.'; parse pull member end call close 'outdd' "free fi(outdd)"
In the following example, all of a PDS's members are updated. A 2-byte packed decimal date field in YY format is converted to a 2-byte binary date that contains the century.
/* REXX */ dsn = "'crude.royalty.data'" parse value listm(dsn) with rc mc mlist address rexxtool "alloc fi(iodd) da("dsn") old" call open 'bpam', 'iodd', 'update' do i = 1 to mc parse var mlist member mlist call findm 'iodd', member parse value get('iodd') with type +1 1 record do while rc = 0 if type = 7 then do parse var record first +25 date +2 last call put 'iodd', first||d2c(p2d(date)+1900,2)||last end parse value get('iodd') with type +1 1 record end end call close 'iodd' address rexxtool "free fi(iodd)" exit
Notes:
/* REXX */ /* First, allocate and open the file 2 times, once for input and once for output */ "alloc fi(indd) da(user.pds) shr reu" "alloc fi(outdd) da(user.pds) old reu" call open 'bpam', 'indd', 'input' call open 'bpam', 'outdd', 'output' /* Next, copy the existing records */ call findm 'indd', 'member1' record = get('indd') do while rc = 0 call put 'outdd', record record = get('indd') end /* Now, add some new records */ do i = 1 to 10 call put 'outdd', 'New record' i end /* Last, replace the directory entry */ call stowm 'outdd', 'member1', 'r' call close 'indd' call close 'outdd' "free fi(indd outdd)" exit
Note: Using this method you can add records anywhere in
the member, not just on the end. You can also omit records on the
copy step, thereby deleting them.
Sharing PDSE Members
A PDSE permits multiple tasks to write to and read from the data
set at the same time. To use PDSE sharing, ensure that all
accessors allocate the PDSE using DISP=SHR (If a component of
your application requires exclusive use of the entire data set,
you can use DISP=OLD to lock-out other users).
A "connection" to a member is established whenever you use the FINDM function. The length of the connection is determined as follows:
Once a program has connected to a member, a temporary version of that member is created and remains in existence until the connection is severed, even though another user may re-write or delete the member.
For a detailed description of PDSE member sharing refer to the
Using Data Sets
publication for your system's level of DFP or SMS.
Sharing PDS Members
If your application requires many users to access a partitioned
data set concurrently, you should use a PDSE instead of a PDS.
As discussed in the previous section, PDSEs provide built-in
support for sharing members. If for some reason your application
cannot use a PDSE, it is possible to share PDS members, although
a little more programming is required.
One way of protecting a PDS while maintaining a relatively high degree of concurrency is to use the REXXTOOLS ENQ and DEQ functions to implement an access protocol. A simple, but effective, protocol follows:
Note: If you are using the REXXTOOLS LISTM function to read the directory, its operation also must be shielded with a shared enqueue.
Note that whenever an application has the capacity to change a PDS directory (i.e., the application contains STOWM function calls) or opens a PDS for output, an enqueue for the entire data set must cover every input/output operation. If a data set-wide enqueue is not obtained for all I/O operations, multiple tasks can write to the data set's free space or to the directory at the same time, irreparably damaging your data. In addition, program abends can occur when a task attempts to read a directory while it is being written to by another task.
If your application is structured so that new members are not created and existing members are not extended (that is, the PDS is pre-formatted, and the only type of write operation performed is an update-in-place), you can increase concurrency by narrowing the scope of your enqueues to include specific member names. However, if your application uses STOWM to update the user field of directory entries, you still must use data set-wide enqueues.
If your application requires a "browsing" function, where information from one or more members is displayed to a user for the purpose of subsequent update, the protocol must be modified slightly in order to maintain a high degree of concurrency while protecting the data set:
Again, the scope of the enqueues used depends, entirely, on
whether or not the directory can be updated by the application.
If the directory can be updated by the application, data set-wide
enqueues must be used. If not, member-specific enqueues can be
used.
BPAM Return Codes
Upon completion, all Shadow REXXTOOLS BPAM functions provide a
return code and a reason code. The return code is placed in the
standard RC variable, while the reason code is placed in a
special variable named REASON.
In addition, except for the GET function which returns a record, all BPAM functions return the return code as their value. For example, the following code will display the return code 2 times, once as OPEN's returned value, and once as the value of the RC variable
say "RC="open('bpam','indd','input') "RC="rc "REASON="reason
Unless otherwise noted, the values for RC and REASON are taken directly from the underlying BPAM macro return and reason codes. In all cases, a return code of zero indicates success.
In the case of GET and PUT, the underlying BPAM macros (READ, WRITE, and CHECK) do not produce return codes. The REXXTOOLS GET function returns RC=8 when end-of-file is reached. All other non-zero GET and PUT return codes are either ABEND codes (which are accompanied by an "IEC" message) or synchronous error exit codes (which are accompanied by a "RXT" message).
Very Important: The complete list of BPAM return and reason codes can be found in the IBM publication Macro Instructions for Data Sets for your system's level of DFP or DFSMS. ABEND codes and "IEC" messages are documented in one or more MVS messages and code publications.
Note: The return and reason codes (including ABEND codes) produced by the BPAM functions are all decimal (not hexadecimal) values. Service Descriptions The sections that follow describe the syntax and operation of the BPAM-related functions.