How to fix a U4038 in a simple program - cobol

I am trying to execute a simple COBOL program on z/OS Mainframe System. The program only opens and closes a file. It compiles with no errors but when I run it I get a U4038 abend.
This is the code of the program:
----+-*A-1-B--+----2----+----3----+----4----+----5----+----6----+----7-|--+----8
IDENTIFICATION DIVISION.
PROGRAM-ID. LISTKSDS
AUTHOR. TestingUser
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INFILE ASSIGN TO DATAIN
ORGANIZATION IS INDEXED
ACCESS MODE IS SEQUENTIAL
RECORD KEY EST-KEY
FILE STATUS IS WS-FILE-STATUS.
DATA DIVISION.
FILE SECTION.
FD INFILE.
COPY EST01.
WORKING-STORAGE SECTION.
01 VARIABLES.
05 WS-FILE-STATUS PIC X(2).
LINKAGE SECTION.
PROCEDURE DIVISION.
DISPLAY "STARTING PROGRAM.."
PERFORM INITIALIZATION
PERFORM TERMINATION
GOBACK.
INITIALIZATION.
DISPLAY "OPENING FILE.."
OPEN INPUT INFILE
IF WS-FILE-STATUS IS NOT EQUAL TO '00'
THEN
GO TO ERROR-EXIT
END-IF.
TERMINATION.
CLOSE INFILE.
ERROR-EXIT.
Job to compile:
//TESTUSEC JOB NERT4587,CLASS=C,MSGCLASS=X,NOTIFY=&SYSUID
//*
//STEP1 EXEC IGYWCL,PARM=(LIB)
//SYSLIB DD DSN=TES.COPIES.TEST,DISP=SHR
//COBOL.SYSIN DD DSN=TES.SOFT.SRC(SRC04),DISP=SHR
//LKED.SYSLMOD DD DISP=SHR,DSN=TES.SOFT.LIB
//LKED.SYSIN DD *
ENTRY LISTKSDS
NAME LISTKSDS(R)
/*
//*
Job to submit:
//TESTUSEC JOB NERT4587,CLASS=C,MSGCLASS=X,NOTIFY=&SYSUID
//*
//JOBLIB DD DSN=TES.SOFT.LIB,DISP=SHR
//*
//STEP1 EXEC PGM=LISTKSDS,REGION=2M
//DATAIN DD DSN=TES.VS.TEST,DISP=SHR
//*

A U4038 abend is a user-abend which comes from Language Environment, the "run-time" for Mainframe programs (it supports multiple Mainframe languages).
You have more information about this. If you look at your spool output, I'd expect you to be able to find more information.
You have specified a FILE STATUS clause on the SELECT for your file, so the U4038 is unlikely to be file-related.
However, it is likely that you have a file problem which is leading to this program problem.
This is likely to be the problem. If it is not the problem, it is a problem:
IF WS-FILE-STATUS IS NOT EQUAL TO '00'
THEN
GO TO ERROR-EXIT
END-IF.
I suspect you are getting a non-zero in WS-FILE-STATUS. The paragraph containing this is PERFORMed, and the GO TO is taking you outside the range of the PERFORM. When you test for a non-zero file-status, it is always a good idea to DISPLAY the non-zero value that you encounter.
Using GO TO outside the range of a PERFORM is especially bad. Your program will continue from the target paragraph of the GO TO and just keep falling through sequentially into the next code.
You have no "next code". So the program "falls off the end". This is not a valid thing to do, so you get an abend.
Although we can't see the content of the copybook, the thing which has given the presumed non-zero file-status probably lies between that, the file definition, and the file you have specified on the DATAIN DD statement.
Most likely is that there is a mismatch in the size of the data defined in your program to the file named in the JCL or the key defined in the program does not match the file in the JCL.
You need to locate the additional message in your spool output. That will normally help a lot. If you can't get it from that, paste the entire SYSOUT output from the step.
You are using a minimal number of full-stops/periods, but it is best to put those on a line of their own, in column 12, so they are not attached to any code. Then you can't copy code and accidentally end up with a full-stop/period in a crucially wrong place.
You would also have received a compiler diagnostic, as your ERROR-EXIT contains no code. Always look at your messages and correct the code accordingly.
Also pay attention to indentation. It has no significance to the compiler, but you format the code for the human reader, so please pay attention to that, as you never know when it will be you some time later, or one of your team-mates at 2am. Or your tutor/mentor reviewing your work.
On your linkedit/bind you have specified DISP=SHR. Change that to DISP=OLD, please. If you manage to run two linkedits at the same time, you can trash your library.

Related

How to pass linkage section data to another program's linkage section in COBOL

I'm working on a Wrapper/Bridge COBOL program that handles program calls and performs cross-cutting operations like logging,security-check etc.
Main motivation is checking the security access for consumer program whether it has access to call the producer program or not.
Let the bridge COBOL program be B1 and the producer program P1 and the consumer(client) C1.
When C1 wants to call P1, it have to make a call to B1. Then, B1 checks the accessibility. If C1 has access, then B1 calls P1 with C1's data.
C1 -> B1 -> P1
In here the linkage section of B1 and P1 are the same. Programs are using EXEC CICS LINK to call each other.
The COMMAREA,
COMMAREA1 (DataSet Name)
01 COMMAREA-STRUCT,
03 a-field
03 another-field
...
The client;
IDENTIFICATION DIVISION.
PROGRAM-ID. Client.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY COMMAREA1
PROCEDURE DIVISION
/* fill CommareaStruct with some values. */
....
/* call B1 Bridge */
EXEC CICS LINK PROGRAM (B1Bridge) NOHANDLE
COMMAREA (COMMAREA-STRUCT)
LENGTH (LENGTH OF COMMAREA-STRUCT)
END-EXEC
....
The Bridge,
IDENTIFICATION DIVISION.
PROGRAM-ID. B1Bridge.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
LINKAGE SECTION.
COPY COMMAREA1
PROCEDURE DIVISION
...
/* access control */
/* logging */
...
/* pass data to P1*/
EXEC CICS LINK PROGRAM (P1) NOHANDLE
COMMAREA (COMMAREA-STRUCT)
LENGTH (LENGTH OF COMMAREA-STRUCT)
END-EXEC
....
The producer ;
IDENTIFICATION DIVISION.
PROGRAM-ID. P1
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
LINKAGE SECTION.
COPY COMMAREA1
PROCEDURE DIVISION
....
*doing some business with data in COMMAREA1
...
When I try above, I got a compile-time warning for Bridge Program B1 ;
"COMMAREA-STRUCT or one of its subordinates was referenced, but COMMAREA-STRUCT was a LINKAGE SECTION item that did not have addressability. This reference will not be resolved succcessfully at execution."
What does it mean? How should I pass B1's linkage section to P1's linkage section?
When I try like this, I got EIBRESP:22 and EIBRESP2: 26 (commarea length error) at runtime.
-- Edit --
I think I should give more details;
Main motivation;
Actually there are two companies that company COM1 and COM2. COM2 was an affiliate of COM1 for several years.
COM1 and COM2 have CICS1 and CICS2 respectively. And COM2 client programs uses COM1 producer programs. COM2 clients never call the COM1 producers directly. COM2 clients put the data into COMMAREA-STRUCT and call a Generic Cobol Program (let it be GCP) remotely. COMMAREA-STRUCT has also "the producer program name" field that GCP figures out which program is wanted to be called. So, GCP exports the data from COMMAREA-STRUCT and maps to the fields of producer. GCP performs the mapping operations dynamically with addressing(not special for each producer). After the producer performs, GCP takes the result and passes back to the client via COMMAREA-STRUCT.
The system was designed like that several years ago. There are thousands of clients of COM2 and thousands of producers of COM1.
Now, COM2 wants to apart from COM1. So COM1 don't want to give full access to all COM1 resources(producers) any more. Thus, COM1 wants to put a new cics in front of the CICS1 which will be a handler CICS that runs only B1 Bridge program locally. This also about network security and company-political decisions.
To seperate the companies from each other in a little while, neither the clients nor the producers should be affected. So, problem should be solved in GCP-Bridge layer.
That's why, B1 Bridge should behave like GCP to the COM2 Clients, should check the accessibility(somehow, we applied it) and should pass all the data that coming from the clients to the GCP without any modification.
Currenty the logging operation does not have any priority. We focus on part the companies in a little while.
So I'm very appreciated for your expert comments.
*We can't use CALL because B1 will be on another CICS and can not access the LOADLIB1 of COM1 thats why B1 should call GCP remotly by EXEC CICS LINK.
*Instead of passing commarea, passing the channel sounds good to me. We will discuss on it.
*By the way, i will check fullword-halfword conflict on LENGHT OF. You are right.
*For the security check, we will discuss on "EXEC CICS QUERY SECURITY".
*As mentioned above, we can not modify copy-books. Only we can change is,
EXEC CICS LINK PROGRAM (GCP)
to
EXEC CICS LINK PROGRAM (B1)
on the clients by find&replace. Because there are thousands of clients. We don't want to change copy-book and touch them.
In lights of these details, I think the problem becomes more understandable.
In a CICS COBOL program invoked via an EXEC CICS LINK, the Linkage Section must contain an 01 level structure with the name DFHCOMMAREA. The precompiler or the COBOL compiler's CICS coprocessor will generate the appropriate USING for the Procedure Division so the program has addressability to the DFHCOMMAREA structure.
DFHCOMMAREA will contain the contents of what you are calling COMMAREA-STRUCT when you LINK to the target program.
One way to deal with the situation in which you find yourself is to modify your copybook to remove the 01 level structure name and require all clients to code their own 01 level structure name just prior to the COPY statement. In the bridge and producer programs this 01 level structure name would be DFHCOMMAREA.
Another way to deal with this situation is to eschew LINK in favor of a dynamic CALL. You would have to include DFHEIBLK as the first parameter of the CALL.
Yet another way to deal with this situation is to eschew commarea for one or more CICS containers. Instead of passing the commarea on the LINK you would pass a channel, the channel would have one or more containers hanging off of it, containing the data you wish to pass.
Something to beware of, you are using the LENGTH OF special register for your commarea length. The special register is a fullword, but the commarea length parameter is a halfword. I suspect that will cause you grief, unless IBM generates code to intercept that particular idiom and move the special register to a temporary halfword (unlikely but possible).
Update:
From your additional information it is apparent your task is to write a "drop-in replacement" for an existing program (the GCP).
A pragmatic approach might be to create a new copybook, let's call it COMAREA2, which is a copy of COMAREA1 but without the embedded 01 level structure name. Place the COPY COMAREA2 statement immediately after the DFHCOMMAREA 01 structure name in the B1 program.
This isn't ideal, as documentation somewhere must make it plain that changes to the COMAREA1 copybook must be reflected in COMAREA2. A manual process like this of course introduces the possibility of error, but it does nicely get you around having to modify any of the C1 or P1 programs.
More elegant, provided it works for you, would be to try...
COPY COMAREA1 REPLACING == 01 COMMAREA-STRUCT== BY ==*01 COMMAREA-STRUCT==.
...in your B1 program. This would remove the need for the COMAREA2 copybook proposed above. Provided this works, you would simply place the COPY statement after the DFHCOMMAREA 01 structure level name.

Does the whole linkage section return?

In program a
EXEC CICS LINK
PROGRAM(PGMB)
COMMAREA(COMMA)
LENGTH(LENGTH OF COMMA)
RESP(CICS-RESP)
END-EXEC
In program b
EXEC CICS RETURN
END-EXEC
Does program b only return the commarea that program a passed? Or does it return the whole LINKAGE SECTION?
Program B returns neither the entire LINKAGE SECTION nor the commarea (COMMA in your example).
It returns nothing.
Why does it return nothing? Because nothing gets passed to it.
Or, rather, what gets passed to it is simply the address(es) of the parameter(s). Nothing else. That is all. Importantly, no length.
PROGA
01 some-stuff.
05 a-bit-of-stuff PIC X.
05 the-rest-of-the-stuff PIC X(99).
CALL .... USING a-bit-of-stuff
PROGB
LINKAGE SECTION.
01 stuff-that-is-somewhere-else PIC X(100).
PROCEDURE DIVISION USING stuff-that-is-somewhere-else.
a-bit-of-stuff is define as only one byte. This makes no difference. It is the definition, in the LINKAGE SECTION, of the item on the PROCEDURE DIVISION USING ... which matches, in order of reference, nothing else, to the CALL ... USING ...
PROGB will be "passed" the address of a-bit-of-stuff. If that address is then mapped to 100 bytes in the LINKAGE SECTION of the CALLed program, COBOL does not mind at all.
If we change that example CALL to use instead some-stuff, since some-stuff has the same starting address as a-bit-of-stuff, there would be absolutely no change in the generated code, and no change in the execution of the two programs.
Defining different sizes of data "between" CALLer and CALLed is usually not done, because it makes things less clear to us, humans. The compiler cares not one jot.
What you need to look at the 01s (or 77s if that silly idea takes your fancy) as is a REDEFINES. They are a REDEFINES, an implicit one, of data which is defined somewhere else. No data is defined for items in the LINKAGE SECTION (there is one exception to that on the Mainframe). The 01-levels in the LINKAGE SECTION are just redefining, or mapping, the address of the data that is passed to the program. The data does not "leave" the CALLing program, and the data is never "passed back".
Things can go wrong, of course, if you use different lengths for matching items on the USINGs. If the storage from the CALLer is "acquired" (like a GETMAIN in CICS) then attempting to referencing data outside of that storage, even one byte further on, can get you an abend due to an Addressing Exception (a S0C4, which CICS will kindly name something else for you, an AKEA).
Even without acquired storage, other fields after the one "passed" can be accidentally trashed, or the field itself may not get the expected amount of data MOVEd to it by the CALLed program, if the definition is short in the CALLed program.
There are actually two things which get "returned" from a CALLed program. They are the special-register RETURN-CODE, and the single item on the RETURNING of the PROCEDURE DIVISION (if used, likely not).
Even so, the mechanisms of how those are achieved are different from the normal misunderstanding of data "passed" between CALLing and CALLed programs.
I have not had the joy of programming CICS for sometime now, this answer is based on what knowledge I still remember.
The calling program gets at most an amount of data less than or equal to the size of the data area sent in the calling program (or as specified by the optional LENGTH parameter). Don't try to access data beyond what you have sent.
"So if program x LINKS to program y, any updates done to the COMMAREA in y will be visible in x."
Source: SOVF:How CICS Shared Memory Works.
"When a communication area is passed by way of an EXEC CICS LINK command, the invoked program is passed a pointer to the communication area itself. Any changes that are made to the contents of the data area in the invoked program are available to the invoking program, when control returns to it; to access any such changes, the program names the data area that is specified in the original COMMAREA option." Source: IBM-CICS-Ref.
So, does program b only return the commarea that program a passed?
I would answer the above as Yes.
Does it return the whole linkage section?
As for this one, it depends on the structure of the DFHCOMMAREA of the linked program. If it only contains 1 such area, then the answer is that it returns as many bytes as was sent on link command from that area (either implicitly or explicitly). Remember that this area is outside your caller. So, if the caller sends 100 bytes and the linkage section has an area of 500 bytes, you only get 100 back at the most.
If you want to allow your linked program to modify data in the commarea, there are some very serious limitations.
Exec CICS
Return
End-Exec
Will expose a changes in a commarea to the LINKing program, but only by accident and only if both tasks execute on the same CICS region. This is because the commarea is actually a pointer. On a distributed program link, the area is copied, but not copied back.

How to reference passed record address in CICS COBOL program?

I am working on a project to convert a mainframe CICS application currently written in HLASM into COBOL. I have a number of utility programs that will continue to be used -- one of them is a "file access" utility which is accessed via CICS LINK. This utility takes the necessary input parameters (passed in the commarea) to generate a CICS file request to read a record from a specified file, and passes back the address of the storage area and length of the retrieved record in that same commarea. In assembler, it was easy to load that address into a register then associate that register with a record map (DSECT) via a USING directive.
But how is this best done in COBOL? Do I use the address passed back in the commarea and somehow associate it with the COBOL record layout so that I can reference a record field directly in the COBOL program? If so, how?
Or do I have to somehow move the data into a local working storage area? And then move it back to reflect any updates that may have been made by the COBOL program? Again, if so, how?
In the CICS COMMAREA declare a variable of type POINTER to hold the address of the record
buffer your utility program will return. For example:
01 COMM-CICS.
02 COMM-SOME-STUFF PIC whatever...
02 COMM-REC-BUFF-ADDRESS POINTER.
02 COMM-REC-BUFF-LENGTH PIC 9(9) BINARY.
02....
In your COBOL program declare the record layout in the
LINKAGE SECTION, for example:
LINKAGE SECTION.
01 LINK-REC.
02 LINK-DATA1 PIC X(10).
02 .....
This creates the layout but does not allocate any
storage to it. Upon return from your utility program use the COBOL SET ADDRESS verb to
assign the address of the record buffer to the record layout, something like:
SET ADDRESS OF LINK-REC TO COMM-REC-BUFF-ADDRESS
Now your COBOL program should be able to address any of the items in the file record by name, for example:
MOVE LINK-DATA1 TO some-other-variable
As a check on the returned data area, you can check the length of the buffer against the length of the COBOL record layout using the COBOL LENGTH OF verb, for example:
IF LENGTH OF LINK-REC NOT = COMM-REC-BUFF-LENGTH
raise an error - buffer length does not match record layout
END-IF
A warning though... This probably will not work if the LINK is to a remote machine because they will not be sharing the same address space. I would recommend that you investigate using CICS Channels and Contaners for this sort of thing.

How to get the Cobol DISPLAY to work in a JCL so that I can see the output message to use them as a debug?

I am working on a Cobol program and need the DISPLAY to print messages so that I can see them when I run the program. I thought that it was SYSOUC that was used to write this type of Cobol Message but I cannot get it to display and it should be so that i can view it and just see the displays or at least that is what I thought.
Question: How do I get the DISPLAY in Cobol to display messages?
//STEP2 EXEC PGM=MyProgram,REGION=32M,COND=(0,NE)
//STEPLIB DD DSN=MyPrgram,DISP=SHR
//SYSOUT DD SYSOUT=1 --system messages
//SYSDBOUT DD SYSOUT=1
//SYSOUC DD SYSOUT=3 --display
//SYSPRINT DD SYSOUT=1
//SYSUDUMP DD SYSOUT=1
//INPUT1 DD DSN=MyFile,DISP=SHR,BUFNO=30
//INPUT2 DD DSN=CDP.PARMLIB(SomeParmCard),DISP=SHR
//OUTPUT1 DD DSN=&&Temp,DISP=(,PASS),UNIT=SYSDA,
// DCB=aaaa.bbbb.MODEL,MGMTCLAS=TMM
//PRTOUTA DD SYSOUT=2,DCB=(BLKSIZE=0,LRECL=133,RECFM=FBM)
By default, from IBM's Enterprise COBOL, your DISPLAY messages are going to //SYSOUT DD SYSOUT=whatyouneedatyoursite
You have marked that as being for system messages, but you should check there. With some utilities (SORT is a good example) that will contain the message output from the product, but that is not the case by default with COBOL.
It is possible to change where the output from DISPLAY goes by compile option: OUTDD(YOURDD). If your output is not on SYSOUT, check the beginning of your compile listings for the options and see if OUTDD is being used.
It is a possibility that logic errors are ensuring that your DISPLAY statements are not executed.
I would recommend putting TIME=(,2) on your EXEC card or your JOB card. This will save you if you happen to get a loop. I still do that today (although get fewer loops than when I was starting out..., many fewer, but if it happens, it'll save a heap of CPU). You may be able to get away with TIME=(,1), but sometimes that chops the dump-processing off in its prime.
When wondering where DISPLAYs that you'e put in the program are, another thing to check is that you are executing the correct version of the program. One thing I find helpful is this:
01 W-WHEN-COMPILED PIC X(8)BX(8).
MOVE WHEN-COMPILED TO W-WHEN-COMPILED
DISPLAY "yourprogramname " W-WHEN-COMPILED
You do the 01 in the WORKING-STORAGE and the MOVE and DISPLAY "once only". Then after any run, you look at the sysout and can tell that you are running the program you compiled (or you can tell that you are not). The date/time should be identical to that on your latest compile listing if that is the one you are running. Saved me lots of time over the years.
There is an Intrinsic Function which gives similar output, including a 4-digit year. That is about the only difference between the two. Both are fully resolved at compile-time, so as long as you only execute them in the "once-only" part of your program, there are no performance implications with either of them. Consult the Enterprise COBOL Language Reference for the documentation of WHEN-COMPILED and the Intrinsic Function, and the Programming Guide for documentation of the compile options.

COBOL read/store in table

The goal of this exercise is to read and store an input file into a table then validate certain fields within the input and output any error records. I need to read and store each policy group so that there are just 5 records stored in the table at a time instead of the entire file.
So I need to read in a policy group which is 5 records, do the processing, then read the next 5 records, etc until the end of the file..
This is the input file.
10A 011111 2005062520060625
20A 011111000861038
32A 011111 79372
60A 0111112020 6 4
94A 011111 080 1
10A 02222 2005082520060825
20A 022221000187062
32A 022221 05038
60A 0222212003 6 4
94A 022221 090 1
....
I was able to load the first 5 records into a table by having my table OCCUR 5 TIMES but I don't know how I would continue that. My code is below. (I wrote it just to see if it was working correctly, but it prints the header line with the first 4 records, instead of just the first 5)
01 TABLES.
05 T1-RECORD-TABLE.
10 T1-ENTRY OCCURS 5 TIMES
INDEXED BY T1-INDEX.
15 RECORD-TYPE-10 PIC X(80).
15 RECORD-TYPE-20 PIC X(80).
15 RECORD-TYPE-32 PIC X(80).
15 RECORD-TYPE-60 PIC X(80).
15 RECORD-TYPE-94 PIC X(80).
copy trnrec10.
COPY TRNREC20.
COPY TRNREC32.
COPY TRNREC60.
COPY TRNREC94.
.....
Z200-READ-FILES.
READ DISK-IN INTO T1-ENTRY(T1-INDEX)
AT END MOVE 'YES' TO END-OF-FILE-SW.
WRITE PRINT-RECORD FROM T1-ENTRY(T1-INDEX).
I don't want a step by step for this (though that'd be nice :P) bc I know WHAT I need to do I just don't know HOW to do it bc my textbook and course note are useless to me. I've been stuck on this for a while and nothing I try works.
I'm assuming that every policy group has exactly 5 records with the 5 record types.
You can set up your working storage like this.
05 T1-RECORD.
10 T1-RECORD-TYPE PIC XX.
10 FILLER PIC X(78).
COPY TRNREC10.
COPY TRNREC20.
COPY TRNREC32.
COPY TRNREC60.
COPY TRNREC94.
Then your read paragraph would look like this. I assumed that TRNREC10-RECORD was the 01 level of the TRNREC10 copybook. if not, substitute the actual 01 levels in the following code.
2200-READ-FILE.
READ DISK-IN INTO T1-RECORD
AT END MOVE 'YES' TO END-OF-FILE-SW.
IF END-OF-FILE-SW = 'NO'
IF T1-RECORD-TYPE = '10'
MOVE T1-RECORD TO TRNREC10-RECORD
END-IF
IF T1-RECORD-TYPE = '20'
MOVE T1-RECORD TO TRNREC20-RECORD
END-IF
...
END-IF.
Your write paragraph would look like this
2400-WRITE-FILE.
WRITE PRINT-RECORD FROM TRNREC10-RECORD
WRITE PRINT-RECORD FROM TRNREC20-RECORD
...
Your processing paragraphs would access the data in the copybook records.
You have textbook, course notes, a manual, an editor, JCL and a computer.
All of those are going to be of use to you, but you've also got to get yourself thinking like your should program.
Your task is to read a file, load five records into a table, do something with them, then write them out.
You will have many tasks where you read a file, do something, and write a file.
So how about getting the file processing down pat first?
Define your files using FILE STATUS
PERFORM OPEN-INPUT-POLICY-MASTER
PERFORM OPEN-OUTPUT-NEW-POLICY-MASTER
In those paragraphs (or SECTIONs, depending on your site standards) OPEN the files, check the file status, abend if not "00".
You will need a READ paragraph. READ in there, check the file status, being aware that "10" is valid and that it indicates end-of-file (so you don't need AT END and END-READ). Count all records read (file status "00").
You will need a WRITE paragraph. Check the file status. Only "00" is valid. Count the records written.
PERFORM PRIMING-READ-OF-POLICY-MASTER
All that paragraph needs to do is PERFORM the READ paragraph. Putting it in a paragraph of its own is a way of documenting what it does. Telling the next person along.
What does it do? Reads, or attempts to read, the first record. If the file is empty, you will get file status "10". If the file should not be empty, abend. You've now dealt with an empty file without affecting your processing logic.
PERFORM PROCESS-POLICY-MASTER UNTIL END-OF-POLICY-MASTER
or
PERFORM UNTIL END-OF-POLICY-MASTER
....
END-PERFORM
I prefer the first, to avoid the main logic "spreading", but it's fine to start with the second if you prefer/it fits in with your course.
The last thing in the paragraph or before the END-PERFORM is a PERFORM of your READ.
You can then PERFORM CLOSE-INPUT-POLICY-MASTER, and similar for the output file.
Then check that the counts are equal. If not, abend. This is trivial in this example, but as your logic gets more complicated, things can go wrong.
Always provide counts to reconcile your input to output, count additions, deletions. Count updates separately for information. Get your program to check what it can. If you have more updates than input records, identify that and abend. Have your program do as much verification as possible.
You will now have a simple program which reads a file, writes a file, checks as much as it can, and is just lacking processing logic.
You can use that program as a base for all your tasks reading one file and writing another.
All that stuff will work in your new program, without you having to do anything.
The logic you need for now is to store data in a table.
OK, as Gilbert has rightly shown, storing in a table doesn't make sense in your actual case. But, it is the requirement. You need to get good at tables as well.
Your table is not defined correctly. Try this:
01 T1-RECORD-TABLE.
05 T1-ENTRY OCCURS 5 TIMES
INDEXED BY T1-INDEX.
10 POLICY-RECORD.
15 POLICY-RECORD-TYPE PIC XX.
15 POLICY-RECORD-DATA PIC X(78).
Put an 88 underneath POLICY-RECORD-TYPE for each of your record-types. Make the 88 descriptive of the business function, don't just say "RECORD-IS-TYPE-10".
You are using an index to reference items in the table. Before putting the first entry in the table you have to SET the index to 1. To access the next entry, you have to SET the index UP BY 1.
Once you have stored your items in the table, you need to get at them again. SET the index to 1 again and you can reference the first entry. SET the index UP BY 1 serially to access the other entries.
SET index TO 1 before you start the processing. MOVE zero to a count of table entries. Get into your file processing loop.
There, count what you store, every time that your count of table entries reaches five, PERFORM a paragraph to output your records and SET your index to 1. If the count is not five, SET your index UP BY 1.
In your paragraph to output the records, use PERFORM VARYING your index FROM 1 BY 1 UNTIL GREATER THAN 5. In the PERFORM, PERFORM your WRITE paragraph with the current table entry as the source for the record.
You will now have two programs, both of which read an input file and produce an identical output file.
Then you can do your verification logic.
If you break everything down, keep things separate, keep things simple, name them well, you'll start to write COBOL programs that are the same except for the specific business logic. All the standard stuff, all the boring stuff, if you like, all the basic structure stays the same. The new code you write is just the specifics of the next task.
Yes, you'll get to read more files, either as reference files, or as multiple inputs. You'll have multiple outputs. But you can build the basics of all those in exactly the same manner. Then you'll have more examples to base your future programs on.
Once you've got the basic stuff, you never need to code it again. You just copy and apply.
With good names, your programs will tell what they are doing.
The code that you actually write will be the "interesting" stuff, not the stuff you do "every time".
I've just "designed" this for you. It is not the only workable design. It is how I do it, and have done for some time. You should also design every part of your processing. You should know what it is doing before you write the code.
As an example, take a simple loop. Imagine how you will test it. With zero entries in the table, what happens? With one? With an intermediate number? One less than the maximum? The maximum? One more than the maximum? 10 more than the maximum? Then write the code knowing that you need to know how to deal with those cases.
In time, not too long, you'll think about the low-level design while you code. In more time, you'll do the high-level design that way as well. In enough time you'll only design things you've not had to deal with before, the rest you'll already know.
You have textbook, course notes, a manual, an editor, JCL and a computer. I've given you some ideas. How about seeing if taken all together they are useful to you. I think you have some frustrations now. Write some basic programs, then apply them to your tasks.

Resources