Fixed-length records
So far every dataset has been LINE SEQUENTIAL: a text file with a line break after each record. That is convenient on a PC, but it is not how z/OS stores data. A mainframe sequential dataset is a string of fixed-length records with nothing between them. No line breaks, no delimiters: record 2 starts at byte 81 of an 80-byte-record dataset simply because record 1 is 80 bytes long.
RECFM and LRECL
Every z/OS dataset carries its format in the catalog:
- RECFM=FB: fixed-length records, grouped into blocks. (
VBmeans variable-length records, each with a small length prefix.) - LRECL=80: the logical record length, in bytes.
- BLKSIZE: how many bytes are read from disk at once. The system picks a good value if you let it.
When a program opens the dataset, z/OS checks the program's record description against the catalog. If the FD says 100 bytes and the dataset has LRECL=80, the OPEN fails with file status 39.
In COBOL
Use plain ORGANIZATION IS SEQUENTIAL (it is also the default), and say
how long the records are:
IDENTIFICATION DIVISION.
PROGRAM-ID. FIXDEMO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT CODE-FILE ASSIGN TO CODES
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD CODE-FILE
RECORD CONTAINS 10 CHARACTERS.
01 CODE-REC.
05 CODE-KEY PIC X(3).
05 CODE-TEXT PIC X(7).
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN INPUT CODE-FILE
READ CODE-FILE
PERFORM UNTIL WS-STATUS NOT = "00"
DISPLAY "[" CODE-KEY "] [" CODE-TEXT "]"
READ CODE-FILE
END-PERFORM
DISPLAY "STATUS " WS-STATUS
CLOSE CODE-FILE
STOP RUN.
The dataset CUR.CODES is 30 bytes on one line, with no line breaks:
EUREURO GBPPOUND USDDOLLAR
Output:
[EUR] [EURO ]
[GBP] [POUND ]
[USD] [DOLLAR ]
STATUS 10
RECORD CONTAINS 10 CHARACTERS documents the length and lets the
compiler check it against the 01 level. On the mainframe you will also
often see BLOCK CONTAINS 0 RECORDS, which means "use the block size
from the dataset", and RECORDING MODE IS F.
When the layout is wrong
With no delimiters, the only thing that marks where a record starts is arithmetic. If the program's layout is one byte out, every record after the first drifts. The same program reading a dataset whose records are really 9 bytes long prints:
[EUR] [EURO G]
[BPP] [OUND US]
[DDO] [LLAR US]
STATUS 10
No error, just garbage. z/OS catches the simple case with status 39 at OPEN, but not a copybook that has the right total length and the wrong fields inside. That is why the record layout (usually a shared copybook) is treated as a contract between every program that touches the file.
Numbers in fixed-length records
Fixed-length records often hold numbers that are not readable text at
all: COMP-3 packed decimal and COMP binary fields, which you will meet
later. In this lesson all fields are DISPLAY digits, so you can still
read the data with your eyes.
On the job
Most test data problems on the mainframe come down to record layout: a file built with an old copybook, a dataset transferred from a PC with line breaks added, a field that grew by two bytes. When a program produces nonsense from a file, compare the LRECL and the copybook before anything else.
Your task
The policy system has sent a mainframe extract of insurance policies: fixed-length 40-byte records with no line breaks. Turn it into a readable report file.
Input, DD name POLICIES, ORGANIZATION IS SEQUENTIAL,
RECORD CONTAINS 40 CHARACTERS:
| Field | Picture | Positions |
|---|---|---|
POL-NO |
X(8) |
1–8 |
POL-HOLDER |
X(20) |
9–28 |
POL-TYPE |
X (A auto, H home, L life) |
29 |
POL-PREMIUM |
9(5)V99 |
30–36 |
FILLER |
X(4) |
37–40 |
Output, DD name POLRPT, line sequential, one line per policy
(declared in the starter as RPT-REC): policy number, holder, type
name (AUTO, HOME, LIFE, or ???? for any other code) and premium
(Z(4)9.99), separated by single spaces:
P0000001 GRACE HOPPER AUTO 1234.50
Then display the control totals:
POLICIES: 004
PREMIUMS: 5084.25
Check the file status after opening the input: if it is not 00,
display POLICIES OPEN FAILED: and the status, and stop with return
code 12.