Published on System iNetwork (http://systeminetwork.com)
Reading IFS Directories in Cobol
By tzura
Created Apr 16 2007 - 07:00

By:
Carlos Balestrazzi [1]

There are many reasons you might want to read IFS directories from a Cobol program:

  1. Display the content of an IFS directory in a "Work with" type user interface
  2. Process the IFS files in some order (e.g., entrance, alphabetical, size) according to your organization's business rules
  3. Cleanup unused or temporary IFS files
  4. Keep track of events that occur in an IFS directory

Whatever the reason, it's good to know that it's possible to write this type of task in a Cobol program with a few IFS APIs.

The sample program I present in this article demonstrates a combination of the first two items. It sorts the files in an IFS directory into alphabetical order, loads them into a subfile, and then presents a "Work with" user interface from which the user can select certain options. To accomplish these objectives, my program uses three IFS APIs: opendir(), readdir(), and closedir(). Also, you use the lstat() API to obtain additional information about each IFS stream file in the directory.

The Program Components

Here are the main steps that the program takes:

  • Create a keyed data queue by calling a CL program.
  • Open an IFS directory with opendir() API.
  • Use a perform loop to read each entry in the directory with the readdir() API, perform the necessary checks on it, and write the obtained information in a data queue entry using QSNDDTAQ API. The loop is terminated when the readdir() function returns a null pointer, meaning no more directory entries to be read.
  • Close the IFS directory with closedir() API.
  • Use a perform loop to read each entry in the data queue using the QRCVDTAQ API and write them to a subfile record. The data queue entries are received in keyed sequence (by alphabetical order). The loop terminates when QRCVDTAQ API returns zeros in the dtaq-length-dta parameter, meaning there are no more data queue entries to be read.
  • Display a "Work with" user interface that lets the user to perform some actions over the IFS files in the directory. This continues until the user presses the F3 key to exit.

Use Data Queues to Sort Data Structures

Data queues were designed to enable inter-program asynchronous communications. In "Balancing the Workload in a Batch Environment" (December 14, 2006, article ID 53698 at SystemiNetwork.com) I demonstrated how to use data queues to separate CPU-intensive from I/O-intensive applications. Now, I am going to use data queues in an unorthodox manner — to sort a data structure. There are more efficient ways to do it, but I find this technique easy to use, and it provides decent performance. The underlying idea is very simple: First, you write entries to a keyed data queue from some data source (in this case from the information returned by readdir() API), and later, when you read the data queue, i5/OS returns each entry in the specified key sequence. In my sample program, I write each data queue entry to a subfile record, so that when it's displayed the user can view the IFS directory entries in alphabetical order.

To create the keyed data queue, I use a CL program. The parameters values for the CRTDTAQ command are passed from the Cobol program to the CL program. This is because the Cobol program knows the necessary values for the MAXLEN and KEYLEN parameters. The value of the MAXLEN parameter must match the subfile record length and the value of the KEYLEN parameter must match the length of the field by the subfile is to be sorted by (in my sample program I use the filename field, which contains the IFS stream file names).

MOVE "QTEMP" TO dtaq-library.
MOVE "TMPDTAQ" TO dtaq-name.
MOVE LENGTH OF subfile-fmt TO dtaq-size.
MOVE LENGTH OF filename IN subfile-fmt TO dtaq-key-length.
CALL "CRTTMPDTAQ" USING dtaq-name,
                        dtaq-library
                        dtaq-size
                        dtaq-key-length.

The opendir() and readdir() IFS APIs

The input parameter for opendir() API is a pointer to a Unix-type variable that contains the path name of an IFS directory. The value returned is a pointer to an open IFS directory handle. This pointer is passed to the readdir() and closedir() IFS APIs so that they know which directory to read from.

CALL PROCEDURE "opendir"
     USING
        BY VALUE ADDRESS OF current-directory
     RETURNING
        directory-handle
END-CALL.
IF directory-handle = NULL
   CALL PROCEDURE "__errno"
        RETURNING errno-pointer
   END-CALL
   SET ADDRESS OF errno TO errno-pointer
   DISPLAY "opendir did not work. Errno: " errno
   PERFORM leave-routine
END-IF.

The current-directory variable is a null-terminated string that contains the IFS directory name that we want to read from. After a successful IFS directory open, you can read the first directory entry:

CALL PROCEDURE "readdir"
      USING
         BY VALUE directory-handle
      RETURNING
         dirent-pointer
END-CALL.
SET ADDRESS OF dirent TO dirent-pointer.

The return value of the readdir API is a pointer to a data structure called dirent. Dirent structure contains the directory entry information at the current position in the directory and must be defined in the LINKAGE SECTION of the Cobol program. Translated from C to Cobol language, dirent structure looks like this:

01  dirent.
    03 d_reserv1           PIC x(16).
    03 d_fileno_gen_id     PIC s9(9) BINARY.
    03 d_fileno            PIC s9(9) BINARY.
    03 d_reclen            PIC s9(9) BINARY.
    03 d_reserv3           PIC s9(9) BINARY.
    03 d_reserv4           PIC x(8).
    03 d_nlsinfo           PIC x(12).
    03 d_namelen           PIC 9(9)  BINARY.
    03 d_name              PIC x(640).

From this structure, my program uses only the last two fields. The d_namelen field contains the length to the file name in bytes, excluding the null terminator, and the d_name field contains the name itself, followed by a null termintor.

You can see that unlike the opendir() API, my program doesn't check the errno global variable if a null value is returned by the readdir() API. That's because the null value means that there are no more entries to be read, so I use this value as an end of file condition:

PERFORM inspect-directory
        UNTIL dirent-pointer = NULL.

The lstat() API

My program needs extra information to build a useful user interface. This extra information is provided by lstat() API.

CALL PROCEDURE "lstat"
      USING
         BY VALUE ADDRESS OF ws-file-name
         BY VALUE ADDRESS OF st_buffer
      GIVING
         return-value
END-CALL.

Ws-file-name is a null-terminated string that contains the name of an IFS stream file and st_buffer is the area to which the information is written. The structure of st_buffer is defined in the IFS API section of the Unix-APIs category in the Information Center. When translated from C to Cobol, you get something like this:

01  st_buffer.
    03 st_mode          PIC s9(9)  BINARY.
    03 st_ino           PIC s9(9)  BINARY.
    03 st_nlink         PIC s9(4)  BINARY.
    03 reserved1        PIC x(2).
    03 st_uid           PIC s9(9)  BINARY.
    03 st_gid           PIC s9(9)  BINARY.
    03 st_size          PIC s9(9)  BINARY.
    03 st_atime         PIC s9(9)  BINARY.
    03 st_mtime         PIC s9(9)  BINARY.
    03 st_ctime         PIC s9(9)  BINARY.
    03 st_dev           PIC s9(9)  BINARY.
    03 st_blksize       PIC s9(9)  BINARY.
    03 st_allocsize     PIC s9(9)  BINARY.
    03 st_objtype       PIC x(10).

Detailed information about each field in this structure can be obtained in the link provided at the end of this article. You can see that my program only uses two fields from st_buffer: st_objtype and st_mtime. If the value of the st_objtype field is not equal to "*STMF", my program simply ignores this directory entry and proceeds to read the next one. You must recall that, unlike System i libraries, IFS directories can contain other directories (called sub-directories). My program skips over sub-directories because it's not able to deal with tree structures (in a future article I will demonstrate how to use recursion in Cobol). The other field my program uses, st_mtime, contains the last change date of the IFS stream file. This date is provided in Unix format: the number of seconds from Jan, 1, 1970 UTC. Okay, this presents a new problem: how do you transform a Unix date to something the user can read on the screen? A series of intrinsic date functions can help accomplish this task:

COMPUTE st_mtime = st_mtime + offset_seconds.
MOVE "1970-01-01-00.00.00.000000" TO epoch.
COMPUTE unix-timestamp-x = st_mtime.
DIVIDE unix-timestamp-x BY 60 GIVING stat-mins
         REMAINDER stat-secs.
MOVE FUNCTION ADD-DURATION (epoch MINUTES stat-mins)
              TO stat-timestamp.
MOVE stat-timestamp TO epoch.
MOVE FUNCTION ADD-DURATION (epoch SECONDS stat-secs)
             TO stat-timestamp.
MOVE FUNCTION convert-date-time
       (stat-timestamp TIMESTAMP) TO chgdate IN subfile-fmt.

Please note that in the initialization routine, my program used the CEEUTCO API to obtain the user's time zone offset from the UTC.

CALL PROCEDURE "CEEUTCO"
       USING BY VALUE ADDRESS OF offset_hours
             BY VALUE ADDRESS OF offset_minutes
             BY VALUE ADDRESS OF offset_seconds
 	 	  OMITTED
END-CALL.

Because lstat() API returns the timestamp in UTC (GMT) time zone, you must add the current UTC offset to the value contained in st_mtime field in order to produce the time in the user time zone. The range of offset_seconds is -43.200 to +46.800.

COMPUTE st_mtime = st_mtime + offset_seconds.

In the above example, chgdate is defined as a date type variable with *EUR format (dd.mm.yyyy), and this is the format that the user will see on the screen.

A Final Note

In this brief program, I count seven Unix-type APIs, three object APIs, one date API, and several intrinsic functions. Indeed, as soon as one gets used to these tools, they're so useful and elegant that one cannot stop using them!

You can download my sample program from the following link:
http://www.pentontech.com/IBMContent/Documents/article/54504_192_ReadDirCobol.zip [2]

If you'd like to experiment with the sample program, you need only change the value of the default-directory-name variable to an IFS directory name that makes sense for your system. Also, be sure to compile the CL program and the display file before the Cobol program. You can compile my sample Cobol program with the following command:

CRTBNDCBL PGM(mysample) SRCFILE(QLBLSRC) + 
             BNDDIR(QC2LE) ACTGRP(*NEW)  

More Information

You can find out more about IFS APIs at:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/unix.htm [3]

© 2010 Penton Media, Inc.

Source URL: http://systeminetwork.com/article/reading-ifs-directories-cobol

Links:
[1] http://systeminetwork.com/author/carlos-balestrazzi
[2] http://www.pentontech.com/IBMContent/Documents/article/54504_192_ReadDirCobol.zip
[3] http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/unix.htm