Still manually managing the IFS? Let's start looking at how to automate it instead.
Many companies, as they exchange information with other businesses, use communication methods such as FTP to send and receive various files. These files, often stored in the IFS of the i, then need to be managed (for example, periodically deleted) when the processing associated with them has completed. This type of file management unfortunately is done manually in all too many cases. This article is the first in a series looking at how you might automate some, if not all, of this management responsibility.
As we have to start somewhere, we'll look today at a program that simply lists the contents of a directory. In terms of helping you immediately manage your system, this initial program is pretty much worthless; using the Work with Object Links (WRKLNK) command will give you more useable information than the provided program will. But, as an introduction to how you can programmatically access an IFS directory, this initial program is quite worthwhile; it allows us to look at one way to access IFS directory information and provides a stepping stone to much more powerful programs in future articles (and programs of your own, customized to your unique needs).
Here is the initial program, named DIR. Note that this program needs to be created as an ILE program on a V5R4 system or higher. OPM CL programs cannot call the system APIs we'll be using shortly, and CRTBNDCL does not support the necessary CL data types, to work with the APIs being utilized, in releases prior to V5R4.
Dcl Var(&Dir_In) Type(*Char) Len(32)
Dcl Var(&Dir) Type(*Char) Len(33)
Dcl Var(&Dir_Ptr) Type(*Ptr)
Dcl Var(&DirEnt_Ptr) Type(*Ptr)
Dcl Var(&DirEnt) Type(*Char) Len(696) +
Dcl Var(&LenOfName) Type(*UInt) Stg(*Defined) +
Dcl Var(&Name) Type(*Char) Len(640) +
Stg(*Defined) DefVar(&DirEnt 57)
Dcl Var(&MsgTxt) Type(*Char) Len(300)
Dcl Var(&Null) Type(*Char) Len(1) +
Dcl Var(&Null_Ptr) Type(*Ptr)
ChgVar Var(&Dir) Value(&Dir_In *TCat &Null)
MonMsg MsgID(MCH3601) Exec(Do)
ChgVar Var(&Dir) Value('.' *TCat &Null)
CallPrc Prc('opendir') Parm((&Dir)) RtnVal(&Dir_Ptr)
If Cond(&Dir_Ptr = &Null_Ptr) Then(Do)
SndPgmMsg Msg('Directory not found') +
CallPrc Prc('readdir') +
Parm((&Dir_Ptr *ByVal)) RtnVal(&DirEnt_Ptr)
DoWhile Cond(&DirEnt_Ptr *NE &Null_Ptr)
ChgVar Var(&MsgTxt) +
Value(%sst(&Name 1 &LenOfName))
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
CallPrc Prc('readdir') +
Parm((&Dir_Ptr *ByVal)) RtnVal(&DirEnt_Ptr)
CallPrc Prc('closedir') Parm((&Dir_Ptr *ByVal))
To compile the DIR program, you can use this command:
The DIR program declares one parameter, &Dir_In. The DIR program, as mentioned, lists the contents of a directory, and the &Dir_In parameter allows you to specify the directory to list. The &Dir_In parameter is defined with a maximum size of 32 bytes. This rather short length is to allow us to easily call the program from a command line, where the default length of a character string is 32 bytes. This parameter could just as easily have been defined with a length in the hundreds or thousands of bytes. But calling the program interactively, for testing purposes, would have been a real pain with these larger lengths.
Skipping over most of the other DCLs for the moment, the first non-declare command found in the DIR program is a Change Variable (CHGVAR). This CHGVAR concatenates the blank-trimmed &Dir_In parameter value with the variable &Null, storing the result in variable &Dir. In order to access the contents of an IFS directory, we will be using the Open Directory (opendir) API, documented here. The opendir API has one input parameter, which is the null terminated path name of the directory to be opened. As the Call Bound Procedure (CALLPRC) command does not provide the ability to null terminate a character string as part of the call operation, the DIR program performs this action using CHGVAR. The &Null variable is declared with a length of 1 byte and is initialized to null (x'00'). The &Dir variable is declared with a length of 33 bytes in order to accommodate the largest supported &Dir_In value (32 bytes) with one null byte concatenated to &Dir_In. Trailing blanks are truncated from the &Dir_In parameter value, using the *TCAT operator, as blanks are significant when specifying IFS names.
Following this CHGVAR command is a Monitor Message (MONMSG) for MCH3601 – Pointer not set for location referenced. If the caller of the DIR program does not provide a &Dir_In parameter value, the DIR program will remove the MCH3601 message from the job log (using the Receive Message (RCVMSG) command) and then set the variable &Dir to a single dot (period) followed by one null byte (&Null).
As mentioned earlier, the variable &Dir is used as the path name input parameter of the opendir API. The opendir path name parameter supports the standard conventions of absolute path names (path names starting with a /), path names relative to the job's current directory (path names not starting with a /), and relative path name notations such as the single dot (representing the current directory) and the double dot (representing the parent directory of the current directory). This assignment of a single, null-terminated dot to &Dir sets the directory to be opened to the current directory of the job, a rather useful default value.
The DIR program then calls the opendir API. The variable &Dir is used as the single input parameter to the API, and the API returns a pointer to the open directory. This pointer value is returned in the variable &Dir_Ptr, which was previously declared with TYPE(*PTR). The API does not document in any way what this returned pointer is pointing to, and in general you do not have to worry about (or manipulate) this returned pointer. The DIR program simply needs to store this pointer value and then pass this value to other APIs later in the program.
You might have noticed the italicized "in general." There is one exception to not having to worry about the pointer value returned from the opendir API. When the path name specified (&Dir) cannot be found (or an error occurs such that we cannot access the directory), the API returns a null pointer value (which is distinctly different from a null byte, the &Null variable). For this reason, the DIR program, after calling opendir, compares the value of the returned &Dir_Ptr to a null pointer (the declared pointer variable &Null_Ptr). If the two variables are equal, the DIR program sends the message "Directory not found" (using the Send Program Message (SNDPGMMSG) command) and then returns. Future articles will look at how more-specific error information can be sent to the user of the DIR program. For now, the program will just indicate that the directory could not be found, without going into the details of why.
There are several ways to test for a null pointer. One approach, which is the one used by the DIR program, is to declare a pointer (&Null_Ptr in our case) using DCL TYPE(*PTR) and then not set an initial address value (that is, do not use the ADDRESS parameter of the DCL command to initialize the declared &Null_Ptr variable). This pointer variable is then, by default, equal to null and can be used to test for equality with other pointer variables.
A second approach, available if the system you're compiling on is V6R1 or later, is to use the special value *NULL as shown here:
If Cond(&Dir_Ptr = *NULL) Then(Do)
This second approach, using the special value *NULL, is much cleaner in terms of not needing to declare the variable &Null_Ptr and more clearly documents what the IF command is really testing. But, as it is not available when compiling the DIR program on V5R4, I chose to use a solution that can be used on a wider range of releases. One interesting tidbit: you can, if you support multiple systems spanning V5R4 through 7.1, use the *NULL support on a V6R1 system and then compile the DIR program to target release (TGTRLS) V5R4. The DIR program will run just fine on this earlier release (even though it's compiled on V6R1 with source utilizing the *NULL special value enhancement, source that could not be compiled directly on V5R4). For additional trivia, this is also true if you use a TGTRLS of V5R3 on your V6R1 compilation system.
Assuming that the directory was successfully opened, the DIR program calls the Read Directory Entry (readdir) API, documented here. The readdir API has one input parameter, the directory to be read, and returns a pointer to a structure that describes the directory entry found. The directory to be read is identified by the &Dir_Ptr value, which was previously returned by the opendir API. This &Dir_Ptr parameter value is passed to the readdir API using the special value *BYVAL, with the reason for specifying *BYVAL to be discussed in a future article. The pointer returned by the readdir API is received into variable &DirEnt_Ptr.
Every call to the readdir API causes a pointer to the "next" directory entry to be returned, with the first call to readdir (that is, the first reference to a &Dir_Ptr value returned by the opendir API) treating "next" as "first." One item worth pointing out is that "first" and "next" are not defined in terms of reflecting any particular sequence. Do not, for instance, expect the directory entries to be returned in alphabetical order. If, when later running the DIR program, the entries appear to be in alphabetical order, it's just by coincidence.
After calling the readdir API, the DIR program then enters a DOWHILE conditioned by the readdir return pointer (&DirEnt_Ptr) not being null. As with the opendir API, a null pointer being returned indicates that no "next" directory entry was found—that is, that all entries have been processed or that an error has been encountered. As with our earlier treatment of a null return pointer from opendir, the DIR program will (for now) assume that all entries have been processed successfully.
The &DirEnt_Ptr pointer variable returned by the readdir API points to a structure providing information about the directory entry. To access this information, the DIR program defines variable &DirEnt. &DirEnt is declared as a TYPE(*CHAR) variable with LEN(696), STG(*BASED), and BASPTR(&DirEnt_Ptr). STG(*BASED) indicates that no storage is to be allocated for the variable—rather that &DirEnt is whatever 696 bytes of storage (the length of &DirEnt) addressed by the pointer variable &DirEnt_Ptr (the BASPTR). As this basing pointer, &DirEnt_Ptr, is the CL variable used to store the return value of the readdir API, this means that the variable &DirEnt is "loaded" with the directory information as soon as the readdir API returns.
There is quite a bit of information available in the &DirEnt variable, but the only two pieces of interest today are the name of the directory entry and the length of the directory entry name. These two pieces of information are defined by the DIR program as variables &Name and &LenOfName, respectively. &Name is declared as a TYPE*CHAR) variable with LEN(640), STG(*DEFINED), and DEFVAR(&DirEnt 57). STG(*DEFINED) indicates that no storage is to be allocated for the variable—rather that &Name is the 640 bytes of storage found at variable &DirEnt starting at location 57. In a similar manner, &LenOfName is declared as a TYPE(*UINT) variable with STG(*DEFINED) and DEFVAR(&DirEnt 53). With this definition, &LenOfName is a 4-byte unsigned integer (the default length of unsigned integers is four bytes) starting at location 53 of variable &DirEnt. The type, location, and length attributes for &Name and &LenOfName were determined from the documentation for the readdir API. The maximum length documented in the Information Center for the &Name variable (640 bytes) is somewhat arbitrary. In working with the root file system (/), the maximum length of any single name imbedded within a path is 255 bytes, so 640 bytes gives us plenty of spare room.
As variables &Name and &LenOfName are defined within variable &DirEnt, and variable &DirEnt (as discussed earlier) is ready to go as soon as the readdir API returns with a non-null &DirEnt_Ptr value, we're all set to display the directory entry. The DIR program now…
- uses the CHGVAR command to set variable &MsgTxt to the name of the directory entry found (using the %sst built-in to access the first &LenOfName bytes of the &Name variable)
- uses the SNDPGMMSG command to display the name of the directory entry
- reads the next directory entry of the &Dir directory by calling the readdir API
- retests the DOWHILE that is used to determine when all directory entries have been processed
When all directory entries have been processed, the DIR program calls one last API: Close Directory (closedir), which is documented here. The closedir API has one input parameter—the directory to be closed—and returns an integer value indicating whether or not the close was successful. The directory to be closed is identified by the &Dir_Ptr value, which was previously returned by the opendir API and subsequently used when calling the readdir API. The DIR program is not currently utilizing the integer return value (which is set to 0 if the close was successful and -1 if an error was encountered). The program simply tries to close the directory and, if something goes wrong, ignores the failure. As mentioned earlier, we'll look at error recovery in future articles.
Having closed the open directory, the DIR program then ends.
To try out the program you can do the following:
- Create a test directory in the root directory using the command CRTDIR DIR('/MyPlayDir').
- Change your current directory to this test directory using the command CHGCURDIR DIR('/MyPlayDir').
- Create a stream file in the test directory using the command EDTF STMF('File1').
- Create a second stream file in the test directory using the command EDTF STMF('File2').
- Run the DIR program using the command CALL PGM(DIR).
You should see the following four messages:
The dot and double-dot entries being displayed are entries that are found in any directory and represent the current directory and parent directory of the directory being processed. In a future version of the DIR program, we will ignore these particular entries. For now, I wanted you to know that they're there. The directory being displayed is /MyPlayDir as this is the current directory (from the second step above), and the DIR program is defaulting to the current directory as no directory parameter was specified when calling DIR in the fifth step above.
Now use this command:
This command creates a subdirectory within your current directory (MyPlayDir). Now call the DIR program again with the command CALL PGM(DIR). You should now see the following five messages:
Though not a stream file, the subdirectory MyImbeddedDir is also being processed by the DIR program. As with the previous dot and double-dot entries, future iterations of the DIR program will handle these subdirectories.
Here are a few other testing possibilities for the DIR program:
- Display the contents of the MyImbeddedDir (just the dot and double-dot entries as we haven't created any stream files or subdirectories within MyImbeddedDir) using a relative path from your current directory:
CALL PGM(DIR) PARM(MYIMBEDDEDDIR)
- Display the contents of the MyImbeddedDir (just the dot and double-dot entries) using an absolute path:
CALL PGM(DIR) PARM('/MyPlayDir/MyImbeddedDir')
- Display the contents of a library (I recommend picking a very small library, not due to any API consideration, but, as the DIR program will send a message for each object found in the library, you might get a bit bored after the first few hundred messages):
CALL PGM(DIR) PARM('/QSys.Lib/SomeLib.Lib')
While simple, the DIR program provides us high-level access to the contents of an IFS directory. In future articles, we will look at how you might develop various management tools as we review additional APIs. We'll also look at the information these APIs can provide us when working with directory entries.
More CL Questions?
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,