Find out how to easily check the contents of IFS subdirectories.
In last month's article, "What IFS Files Have Not Been Used For Three or More Days?," we saw how a CL program can easily determine how many days it's been since a file has been used. We also looked briefly at how we could automate the removal of files that have not been accessed within a user-determined number of days. Today, we will expand on last month's program with more specific handling of the stream files (*STMF) and directories (*DIR) that are processed by the DIR3 program.
The USEDIRPGM command currently lists the contents of the directory specified by the DIR parameter. First, we'll add a parameter to the USEDIRPGM command to optionally list the contents of any subdirectories found in the specified directory. This might be useful if, for instance, you have a main directory for transaction files and, within this directory, subdirectories for transaction files by company, day, or some other tracking mechanism.
In keeping with how the USEDIRPGM command is currently defined, we'll first add message description DIR0004 to the USERMSGF message file using the following command.
ADDMSGD MSGID(DIR0004) MSGF(USERMSGF) MSG('Directory subtree')
The new SUBTREE parameter is defined within the USEDIRPGM command as shown below in bold. The parameter is defined as optional with two valid values: *ALL and *NONE. The default is *ALL, indicating that all subdirectories of the directory identified by the DIR parameter should be processed.
CMD PROMPT(DIR0001)
PARM KWD(DIR) TYPE(*PNAME) LEN(1024) DFT(*CURDIR) +
SPCVAL((*CURDIR '.')) PROMPT(DIR0002)
PARM KWD(DAYS) TYPE(*UINT4) DFT(3) PROMPT(DIR0003)
PARM KWD(SUBTREE) TYPE(*CHAR) LEN(10) RSTD(*YES) +
DFT(*ALL) VALUES(*ALL *NONE) PROMPT(DIR0004)
To create the USEDIRPGM command, you can use the following command.
CRTCMD CMD(USEDIRPGM) PGM(DIR3) PMTFILE(USERMSGF)
The CPP for USEDIRPGM, DIR3, is shown below with the changes bolded.
Pgm Parm(&Dir_In &Days &SubTree)
Dcl Var(&Dir_In) Type(*Char) Len(1024)
Dcl Var(&Days) Type(*UInt)
Dcl Var(&SubTree) Type(*Char) Len(10)
Dcl Var(&InlPath) Type(*Char) Len(1025)
Dcl Var(&Dir_Ptr) Type(*Ptr)
Dcl Var(&DirEnt_Ptr) Type(*Ptr)
Dcl Var(&DirEnt) Type(*Char) Len(696) +
Stg(*Based) BasPtr(&DirEnt_Ptr)
Dcl Var(&LenOfName) Type(*UInt) Stg(*Defined) +
DefVar(&DirEnt 53)
Dcl Var(&Name) Type(*Char) Len(640) +
Stg(*Defined) DefVar(&DirEnt 57)
Dcl Var(&FileInfo) Type(*Char) Len(128)
Dcl Var(&LstAccess) Type(*Int) +
Stg(*Defined) DefVar(&FileInfo 25) /* Last open */
Dcl Var(&LstDtaChg) Type(*Int) +
Stg(*Defined) DefVar(&FileInfo 29) /* Last changed*/
Dcl Var(&ObjTyp) Type(*Char) Len(10) +
Stg(*Defined) DefVar(&FileInfo 49) /* Object type */
Dcl Var(&DatTim_Ptr) Type(*Ptr)
Dcl Var(&DatTim) Type(*Char) Len(24) +
Stg(*Based) BasPtr(&DatTim_Ptr)
Dcl Var(&SecsInDay) Type(*Int) Value(86400)
Dcl Var(&TimeFilter) Type(*Int)
Dcl Var(&Path) Type(*Char) Len(10000)
Dcl Var(&MsgTxt) Type(*Char) Len(300)
Dcl Var(&Status) Type(*Int)
Dcl Var(&Null) Type(*Char) Len(1) +
Value(x'00')
Dcl Var(&Null_Ptr) Type(*Ptr)
ChgVar Var(&InlPath) Value(&Dir_In)
ChgVar Var(&Path) Value(&InlPath *TCat &Null)
CallPrc Prc('opendir') Parm(&Path) RtnVal(&Dir_Ptr)
If Cond(&Dir_Ptr = &Null_Ptr) Then(Do)
SndPgmMsg Msg('Directory not found') +
ToPgmQ(*Ext)
Return
EndDo
CallPrc Prc('time') Parm((*Omit)) RtnVal(&TimeFilter)
ChgVar Var(&TimeFilter) +
Value(&TimeFilter - (&Days * &SecsInDay))
CallPrc Prc('readdir') +
Parm((&Dir_Ptr *ByVal)) RtnVal(&DirEnt_Ptr)
DoWhile Cond(&DirEnt_Ptr *NE &Null_Ptr)
If Cond(%sst(&Name 1 1) *NE '.') Then(Do)
ChgVar Var(&Path) Value(&InlPath *TCat '/' *TCat +
%sst(&Name 1 &LenOfName) *TCat &Null)
CallPrc Prc('stat') +
Parm(&Path &FileInfo) RtnVal(&Status)
If Cond(&Status = 0) Then(Do)
Select
When Cond((&ObjTyp = '*DIR') *And +
(&SubTree = '*ALL')) Then(Do)
ChgVar Var(&MsgTxt) +
Value('Starting imbedded directory: ' +
*Cat %sst(&Name 1 &LenOfName))
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
ChgVar Var(&Path) Value(&InlPath *TCat +
'/' *TCat +
%sst(&Name 1 &LenOfName))
UseDirPgm Dir(&Path) Days(&Days) SubTree(*ALL)
ChgVar Var(&MsgTxt) +
Value('Ended imbedded directory: ' +
*Cat %sst(&Name 1 &LenOfName))
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
EndDo
When Cond((&ObjTyp = '*STMF') *And +
(&LstAccess < &TimeFilter)) +
Then(CallSubr Subr(DspStmF))
When Cond(&ObjTyp = '*STMF') Then(Do)
ChgVar Var(&MsgTxt) +
Value(&ObjTyp *TCat ' ' *Cat +
%sst(&Name 1 &LenOfName) *TCat +
' recently accessed')
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
EndDo
Otherwise Cmd(Do)
ChgVar Var(&MsgTxt) +
Value(&ObjTyp *TCat ' ' *Cat +
%sst(&Name 1 &LenOfName) *TCat +
' not processed')
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
EndDo
EndSelect
EndDo
Else Cmd(Do)
ChgVar Var(&MsgTxt) Value('** ERROR **' +
*Cat %sst(&Name 1 &LenOfName))
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
EndDo
EndDo
CallPrc Prc('readdir') +
Parm((&Dir_Ptr *ByVal)) RtnVal(&DirEnt_Ptr)
EndDo
CallPrc Prc('closedir') Parm((&Dir_Ptr *ByVal))
Subr Subr(DspStmF)
ChgVar Var(&MsgTxt) +
Value(&ObjTyp *Cat %sst(&Name 1 &LenOfName))
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
CallPrc Prc('ctime') +
Parm(&LstDtaChg) RtnVal(&DatTim_Ptr)
ChgVar Var(&MsgTxt) +
Value('Last change: ' *Cat &DatTim)
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
CallPrc Prc('ctime') +
Parm(&LstAccess) RtnVal(&DatTim_Ptr)
ChgVar Var(&MsgTxt) +
Value('Last access: ' *Cat &DatTim)
SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)
EndSubr
EndPgm
As with last month's article, you can create the DIR3 program on V5R4 using the two following commands.
CRTCLMOD MODULE(DIR3)
CRTPGM PGM(DIR3) BNDDIR(QC2LE)
If your system is V6R1 or later, you can also use the single command shown below.
CRTBNDCL PGM(DIR3)
Two changes that are related to the new SUBTREE keyword of the USEDIRPGM command are the addition of the parameter &SUBTREE on the PGM command and the DCL for &SUBTREE.
The next changes within the program are not until the DO group associated with a successful return value from the stat API. As the DIR3 program will now be doing specific processing based on the characteristics of the object encountered in the directory, I elected to implement a SELECT group.
Within the SELECT group, the first WHEN command checks if a subdirectory has been encountered and if the SUBTREE special value *ALL was used when running the USEDIRPGM command. If so, the program performs the following processing.
- Sends a message indicating that processing of the imbedded directory is starting
- Formats the &Path variable by concatenating the directory specified by the DIR parameter of the USEDIRPGM command with the slash character (/) and the name of the subdirectory found
- Runs the USEDIRPGM command using the &Path variable formatted in step 2 for the DIR parameter, the &DAYS value initially passed to the program for the DAYS parameter, and SUBTREE(*ALL)
- Sends a message indicating that the processing of the imbedded directory is complete when the USEDIRPGM command run at step 3 returns
- Passes control to the associated ENDSELECT command as the WHEN-related DO group is finished. At this point, the next directory entry is read and the DOWHILE group processes the next entry.
The running of the USEDIRPGM command in step 3 above processes the specified (sub) directory in the same manner the initial directory was processed, including running further USEDIRPGM commands as additional subdirectories are discovered at lower levels of the directory tree. Coding SUBTREE(*ALL)—or SUBTREE(&SubTree), which is equivalent given the previous WHEN check that &SubTree is *ALL—is done just in case someone has used the Change Command Default (CHGCMDDFT) command and changed the default for SUBTREE to *NONE. As we know that *ALL was specified when initially running the USEDIRPGM command (in order to get to this part of the program), we want to ensure that *ALL is continued for all subsequent uses of the command.
This ability for a command, or more accurately the command CPP, to call itself can, as you can see, really streamline some of our processing. And a nice thing about CL is that CL programs are, by default, recursive. That is, a CL program can safely call itself multiple times. This characteristic unfortunately is not true, by default, for all languages. An RPG program, for instance, if it directly or indirectly calls itself, will fail with the escape message RNX8888—the program was called recursively—if the program is created using default creation values. RPG does have recursive capabilities, but you need to put a bit (though not a lot) more thought into it.
One caution concerning recursive use of the USEDIRPGM command, though: the DIR parameter of the command is arbitrarily defined with a length of 1024 bytes. If you have deeply nested directories and/or long directory names, you may want to increase the size of this keyword definition when using SUBTREE(*ALL).
The additional changes found in the DIR3 program are unrelated to the processing of subdirectories when running the USEDIRPGM command.
The second WHEN command checks if a *STMF has been encountered and if the time of last access is earlier than the value of &TimeFilter. If so, the subroutine DspStmF is run. The actual processing found in the DspStmF subroutine is the same as last month. Moving this processing into a subroutine was simply an easy way to get the code out of the mainline, which was beginning to look a little cluttered.
The third WHEN command identifies those *STMFs that have been accessed more recently than the value of &TimeFilter. In this situation, DIR3 sends a message indicating that the *STMF was processed and that it has been recently accessed.
The OTHERWISE command catches all situations not previously matched up with a WHEN command. This might be a subdirectory where SUBTREE(*NONE) was specified or an object type other than *DIR and *STMF. In this case, DIR3 sends a message informing the user that the object was not processed.
To demonstrate running the enhanced USEDIRPGM command, imagine that there is a directory /MyPlayDir with the following contents:
. DIR
.. DIR
file1.txt STMF
file2.txt STMF
MyImbeddedDir DIR
Within the directory MyImbeddedDir, which is found in /MyPlayDir, is:
. DIR
.. DIR
delete DIR
imbedded_test_file STMF
And within the directory Delete, which is found in /MyPlayDir/MyImbeddedDir, is:
. DIR
.. DIR
Running the command USEDIRPGM DIR('/MyPlayDir') will result in messages similar to the following (assuming that all of the *STMFs have been accessed within the last three days).
*STMF file2.txt recently accessed
Starting imbedded directory: MyImbeddedDir
Starting imbedded directory: delete
Ended imbedded directory: delete
*STMF imbedded_test_file recently accessed
Ended imbedded directory: MyImbeddedDir
*STMF file1.txt recently accessed
Running the command USEDIRPGM DIR('/MyPlayDir') SUBTREE(*NONE) will result in messages similar to the following:
*STMF file2.txt recently accessed
*DIR MyImbeddedDir not processed
*STMF file1.txt recently accessed
Next month, we'll look at some additional capabilities that can be added to the USEDIRPGM command to help you manage your IFS.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7, V6R1
LATEST COMMENTS
MC Press Online