Writing IFS Stream Files in Cobol

Article ID: 54223

When I began to investigate how to access IFS stream files from Cobol programs, my co-workers asked me, "Why not simply continue using iSeries Access for Windows Data Transfer, or some IFS command like CPYFRMIMPF or CPYTOIMPF?" It's a valid question, considering the high learning curve required — particularly if you have to learn by reading the IBM IFS API documentation, which is written in C jargon.

In Scott Klement's article "RPG and the IFS: Text Files in the World" (System iNEWS, January 2005, http://www.systeminetwork.com/article.cfm?id=19626), I found many good reasons to learn about using IFS APIs in HLLs. Now, I want to add another reason to Scott's list: Using IFS APIs in my own programs helps me satisfy the requirements of the bureaucracy.

Dealing with the Bureaucracy

In my country (as in many countries), banking activity is highly regulated by the State. The Central Bank is responsible for the supervision of the financial institutions, and therefore we must continuously send it financial information. When the Central Bank requires something from us, we simply must do it. No option; they have all the State authority. Now, from this perspective imagine the following requirements (this is a true story; "only the names have been changed to protect the innocent"): Within a 24-hour period, we had to send the Central Bank a comma-separated value (CSV) file with the name, address, and country of all our clients who reside abroad. That sounds easy, and we have done more complex things in the past. So we extracted the information from our client database and inserted the records into a temporary file with the right fields for this purpose:

INSERT INTO temp
   SELECT client_name, client_address, client_country
       FROM clients
       WHERE residence_code = 'FOREIGN'

Next, we ran the iSeries Access Data Transfer and selected CSV as the file type for the output PC file. Unfortunately, this didn't work as well as we'd hoped. The next day, we received a reject notification, telling us that the CSV file did not match the requirements. Data Transfer from iSeries Access had created a PC file in this format:

"Floyd Austin","Address 1","U.S.A"+CRLF
"Jack Beale","Address 2","ENGLAND"+CRLF

The bureaucrats wanted the string delimiter ('"') and the field delimiter (",") in fixed positions, as follows:

"Floyd Austin      ","Address 1               ","U.S.A     "+CRLF           
"Jack Beale        ","Address 2               ","ENGLAND   "+CRLF

They didn't tell us why they needed this, but it didn't matter, because we had to find a quick solution. We wanted a solution that would let us control precisely how the file was formatted, so that no matter what sort of requirements were given to us, we'd be able to meet them! That's when the IFS APIs started to look attractive.

Using IFS APIs Inside a Cobol Program

My co-workers' resistance was understandable. As Cobol programmers, we are skilled in dealing with business problems. Cobol protects us against low-level programming by providing a high level of abstraction. Cobol was designed to solve business problems, and for that purpose, it's the best data processing language. Imagine seeing the following clause in a Cobol program:

01   quantity-in-stock        PIC 9(5) VALUE 530
or
01   part-number              PIC 9(4) VALUE 20.

There's no doubt about the meaning of these values. In the first case, the value of the variable quantity-in-stock reflects the attribute of an entity instance (in this example, there are 530 units of the piece number 20 in the warehouse). In the second example, the value of part-number represents the identification (or primary key) of the entity instance Part Number 20, and with this value you can obtain the rest of its attributes from the database.

It might amuse you to note that the English word "value" has a Latin (Romance) origin, and a 19th century thinker once observed "This is quite in accordance with the spirit of a language that likes to use a Teutonic word for the actual thing, and a Romance word for its reflexion."(1)

Now, suppose you read the next clause in the same Cobol program:

access_rights   PIC S9(9) BINARY VALUE 416   

What does the value 416 represent (or reflect) here? The number 416 represents… itself, that is, the binary value 110100000, where each bit is a "flag" that you must supply to the open() API at the moment to establish the file access permissions.

Obviously, Cobol programmers feel uncomfortable with this approach, because we don't habitually have to deal with binary notation, octal to decimal conversion, or other issues of this nature.

It's also worth noting that C/C++ programmers have an advantage over Cobol programmers: IBM provides them with a complete set of copybooks (or, "include files") that they can use in their programs. These copybooks greatly facilitate this work. I have tried to make something similar for a Cobol environment, so that you can forget bits and bytes and concentrate on the really important thing: Solving your organization's business problems. At the end of this article, I provide a link to this copybook. I tried to maintain the same field nomenclature that IBM used in its QSYSINC/H/FCNTL copybook for ILE C:

*-----------------------------------------------*
* oflag Values for open()                       *
*-----------------------------------------------*
01  o_rdonly         PIC s9(9) BINARY VALUE 1.          
01  o_wronly         PIC s9(9) BINARY VALUE 2.          
01  o_rdwr           PIC s9(9) BINARY VALUE 4.          
01  o_creat          PIC s9(9) BINARY VALUE 8.          
01  o_excl           PIC s9(9) BINARY VALUE 16.         
01  o_ccsid          PIC s9(9) BINARY VALUE 32.         
01  o_trunc          PIC S9(9) BINARY VALUE 64.  
01  o_codepage       PIC s9(9) BINARY VALUE 8388608.    
01  o_textdata       PIC s9(9) BINARY VALUE 16777216.   
01  o_text_creat     PIC s9(9) BINARY VALUE 33554432.

In my copybook, the values of these variables are represented in decimal notation, whereas in the FCNTL copybook, they are represented in octal notation.

When you build the oflag parameter for the open() API, you can select from the preceding variables those that you need. For example, suppose you need to open an output file, create it if doesn't exist, or clear it if it does. The code in the PROCEDURE DIVISION could be as in this example:

COMPUTE open-flag = o_wronly    + 
                    o_creat     + 
                    o_ccsid     + 
                    o_trunc     + 
                    o_textdata  + 
                    o_text_creat  
END-COMPUTE.

As a result of this statement, open-flag variable "value" 50331754, which translated to binary notation results in…Ha! It doesn't matter! Surely it will be a long row of zeros and ones that will specify the right capabilities to the open() API, but I don't have to know what they are, because my copybook does all the work for me. The open() API will receive this variable as a string of 32 bits where each bit is a flag that enables or disables a specific opening attribute.

These are the IFS APIs that I use in the code download for this article:

  • open()
  • write()
  • close()

As in my previous article, "Reading Stream Files in Cobol" (http://www.systeminetwork.com/article.cfm?ID=54056), here I highlight only some Cobol-specific aspects in the use of these APIs. For exhaustive explanation about the use of these APIs in RPG programs, check out the links supplied in my previous article and in the Scott Klement's e-book Working with the IFS in RPG IV (http://www.scottklement.com/rpg/ifs_ebook/index.html). The concepts expressed in those documents are completely applicable to a Cobol environment.

The open() API

Now, let me explain some detail about the open() API. When you understand how open() works, you can apply the same considerations to the other APIs, and they should be easy enough to understand.

The open() API is called in this way:

CALL PROCEDURE "open"               
    USING                           
        BY VALUE     ADDRESS OF stream-filename
        BY VALUE     open-flag      
        BY VALUE     access-rights       
        BY VALUE     codepage-ascii 
        BY VALUE     conversion-id  
    RETURNING                       
        file-descriptor             
END-CALL.

Because the open() API can receive a varying number of parameters, it requires an operational descriptor when it's called from Cobol. When you pass a descriptor, the Cobol program provides additional information about the parameters so that the API can understand which ones you've passed.

In my program, I use the following clause to tell the compiler that it must create an operational descriptor:

SPECIAL-NAMES.                              
    LINKAGE TYPE IS PROCEDURE FOR "open"    
              USING ALL DESCRIBED.

If an operational descriptor doesn't exist, the open() API fails and returns a -1 to your program. This prevents the program from working correctly.

When I must pass a parameter of type character, I prefer to use the special register ADDRESS OF. This way, the compiler creates a pointer that contains the address of the variable. This pointer is passed to the open() API with the USING BY VALUE clause. The character variable must contain a "Unix-type" string (i.e., a null-terminated string).

I assign the values of the integer parameters, selecting the appropriate variables from those provided in the copybook and adding them together, as I already demonstrated for the oflag parameter.

You can see after all, that the resulting program is very simple and short. After you write one, the skeleton can be reused many times and for many unpredicted situations.

More Information

In addition to the preceding links, information about IFS APIs can be found at:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/unix.htm

Information about operational descriptors can be found at:
http://publib.boulder.ibm.com/infocenter/iadthelp/v6r0/index.jsp?topic=/com.ibm.etools.iseries.pgmgd.doc/cpprog512.htm

You can download the sample code for this article from the following link:
http://www.pentontech.com/IBMContent/Documents/article/54223_178_CobolStmf.zip


Footnote 1:"The natural worth of anything consists in its fitness to supply the necessities, or serve the conveniences of human life." (John Locke, "Some Considerations on the Consequences of the Lowering of Interest", 1691, in Works Edit. Lond., 1777, Vol. II., p. 28.) In English writers of the 17th century, we frequently find "worth" in the sense of value in use, and "value" in the sense of exchange value. This is quite in accordance with the spirit of a language that likes to use a Teutonic word for the actual thing, and a Romance word for its reflexion. (Karl Marx, Capital, Chapter 1, Section 1)

ProVIP Sponsors

ProVIP Sponsors