The CL Corner: Isn't Recursion Great?

  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

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)                                


             SPCVAL((*CURDIR '.')) PROMPT(DIR0002)        



             DFT(*ALL) VALUES(*ALL *NONE) PROMPT(DIR0004) 


To create the USEDIRPGM command, you can use the following command.




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) +              


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') +             





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)                       



                    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)        



                    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)        



                    Otherwise Cmd(Do)                               

                         ChgVar Var(&MsgTxt) +                       

                           Value(&ObjTyp *TCat ' ' *Cat +           

                                 %sst(&Name 1 &LenOfName) *TCat +   

                                 ' not processed')                  

                         SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)        





              Else Cmd(Do)                                       

                   ChgVar Var(&MsgTxt) Value('** ERROR **' +     

                     *Cat %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))               


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)                     




As with last month's article, you can create the DIR3 program on V5R4 using the two following commands.





If your system is V6R1 or later, you can also use the single command shown below.




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.


  1. Sends a message indicating that processing of the imbedded directory is starting
  2. 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
  3. 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)
  4. Sends a message indicating that the processing of the imbedded directory is complete when the USEDIRPGM command run at step 3 returns
  5. 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 This email address is being protected from spambots. You need JavaScript enabled to view it.. I'll try to answer your burning questions in future columns.

as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7, V6R1