There are many reasons you might want to read IFS directories from a Cobol program:
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.
Here are the main steps that the program takes:
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 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.
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.
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)
You can find out more about IFS APIs at:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/unix.htm [3]
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