The CL Corner: When Was an IFS File Last Used or Changed?

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

Use the stat API to get important IFS information.


In last month's article, "Trying to Get a Handle on Your IFS?," we saw how a CL program can determine what files are in an IFS directory. Today, we'll take that one step further: finding out when the files were last opened and when they were last changed. This is the type of information that may help you decide what files are ready to be archived and/or deleted on your system.


To get the file status information, we will use the system API Get File Information (stat), which is documented here. The stat API returns quite a bit of information concerning an IFS file. In addition to the previously mentioned time that  a file was last opened and last updated, the API also returns information such as the file type (for example, *STMF, *DIR, *PGM, and *FILE) and the size of the file. Here is a modified version of last month's DIR program, utilizing the stat API, renamed to DIR2.  


Pgm        Parm(&Dir_In)                               

Dcl        Var(&Dir_In)     Type(*Char) Len(32)        


Dcl        Var(&InlPath)    Type(*Char) Len(32)        

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(&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)                   

MonMsg     MsgID(MCH3601) Exec(Do)                        

           RcvMsg MsgType(*Last)                           

           ChgVar Var(&InlPath) Value('.')                



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


                 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)            



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




Depending on the release level of the i operating system you are running, you can create the DIR2 program with either one or two steps. If you are on V6R1 or later, you can use this single command:




If you are on V5R4, you will need to use the following two-step process (as the QC2LE binding directory is not implicitly used by CRTBNDCL if the system release level is prior to V6R1):




The Declares

As with the original DIR program, DIR2 defines the input parameter &Dir_In. A small change has been made, however, in the declare following &Dir_In. Where we previously defined the 33-byte &Dir variable to represent the null-terminated value of &Dir_In, we now declare the variable &InlPath to represent the initial path to be searched using the opendir, readdir, and closedir APIs introduced last month. The &InlPath variable is defined with the same length as &Dir_In as we will not always be null terminating the path name passed to the DIR2 program. As you will see shortly we will, when using the stat API, directly concatenate file names to the initial path name, in which case the null byte of the original DIR program's &Dir variable gets in the way.


After declaring the &InlPath variable, DIR2 defines the &DirEnt_Ptr pointer and the &DirEnt structure as was done in the DIR program. There is no change in this definition from the original DIR program.


Following the &DirEnt structure, however, is a new structure: &FileInfo. The stat API, when passed an IFS file name, returns a fixed-length structure of128 bytes. The declared &FileInfo variable is defined to hold this returned information. Defined as subfields of &FileInfo are the three variables of interest to us today: &LstAccess,  &LstDtaChg, and &ObjTyp, which correspond to the time of last access (or open) to the file, the time that data within the file was last changed, and the type of object, respectively. Near the end of this article, for those of you interested, you can find the entire CL declaration of the &FileInfo structure. The &FileInfo structure definition is based on the documentation found in the Information Center, with the mechanics of how I created the equivalent CL DCLs to be discussed in a future column of "The CL Corner."


You might (or might not) have noticed that the time values returned by the stat API (&LstAccess and &LstDtaChg) are declared as signed integer variables. Many UNIX-type APIs (the opendir, readdir, closedir, and stat APIs are considered UNIX-type) work with time values as 4-byte integers representing the number of seconds elapsed since midnight (Coordinated Universal Time, or UTC) January 1, 1970. This time base is often referred to as the UNIX epoch. To convert a time value such as 1,298,147,931 seconds since Jan 1, 1970, to a more human-readable form such as Sat Feb 19 15:38:51 2011 (assuming you are in Rochester, Minnesota), we will be using the Convert Time to Character String API ctime (which converts UTC time values to local time, which is why I mentioned being in Rochester, Minnesota; the time you see for a time value of 1,298,147,931 may differ) documented here. The next declared variables, &DatTim_Ptr and &DatTim, are used to access the character form of the time returned by the stat API. We'll discuss these variables more when we later call the ctime API.


The DIR2 program, in addition to the variables &MsgTxt, &Null, and &Null_Ptr, which were used by the original DIR program, also declares the variables &Path and &Status. &Path is defined as a 10,000-byte character variable to hold the name of the file being accessed by the stat API (with 10,000 bytes simply being an arbitrarily large variable allocation), and &Status is defined as an integer value to hold a return value (indicating success or failure) when calling the stat API.

What DIR2 Does

With these declares out of the way, let's now look at the actual processing done by the DIR2 program.


Similar to the DIR program, DIR2 first determines, by monitoring for escape message MCH3601, whether the &Dir_In parameter was passed to the DIR2 program. If a &Dir_In parameter was provided, the &InlPath variable is set to the user-specified &Dir_In value. If the &Dir_In parameter was not provided, the variable &InlPath is set to the thread's current directory. The variable &Path is then set to the null-terminated &InlPath value and the directory specified by &Path opened using the opendir API. Assuming that the directory is successfully opened, the first directory entry is read using the readdir API, and a DOWHILE loop is entered to process all of the entries found within the directory. So far, the processing is the same as that used in last month's column. But that's about to change!


As mentioned last month, the DIR program displayed the special entries dot and double-dot so that you would be aware that they are returned by the readdir API. The DIR2 program, as promised, ignores these entries by the simple test of checking for a leading dot in the readdir returned file name. If the file name does not contain a dot as the first byte, DIR2 calls the stat API with the null-terminated concatenated initial path name (&InlPath) and the readdir-returned file name.


The stat API defines two parameters and a return value. The first parameter is the null-terminated path name of the file to be accessed (&Path), and the second parameter is the receiver variable to use by stat when returning information about the file (&FileInfo). If the API call is successful, a return value (&Status) of 0 is returned. A return value of negative 1 indicates that the API encountered an error. A future article will look at how you can determine what type of error occurred. For now, DIR2 simply displays the message ** ERROR **, along with the file name, if an error situation arises.


Assuming that no error was detected when calling stat, DIR2 then displays three messages for each file processed:


  • The type of file (&ObjTyp) along with the file name (&Name)
  • The text "Last change:" along with the time the file was last changed (a formatted &DatTim value based on &LstDtaChg)
  • The text "Last access:" along with the time the file was last accessed/opened (a formatted &DatTim value based on &LstAccess)


The &DatTim value is a character string formatted as day of week, month, day, hour, minute, second and year in local time. This character string is the result of calling the ctime API. ctime defines one parameter and a return value. The parameter, as mentioned earlier, is an integer value representing the number of seconds elapsed since the UNIX epoch. The integer can be a negative or positive value and can be used to represent times up to Jan 19 2038 03:14:07 UTC. The return value is a pointer to the character string representation of the integer value. As a pointer to the character value is returned, DIR2 defines the pointer &DatTim_Ptr and then the based variable &DatTim to access the first 24 bytes of the date and time. This approach, a return pointer and the use of based variables to access the underlying data values, is similar to how directory entry information is accessed when calling the readdir API and receiving back a &DirEnt_Ptr pointer value. The date and time returned by ctime is actually 26 bytes in length, but, as the last two bytes are a new line-control character and a null byte that the CL program does not need, DIR2 simply ignores the last two bytes.


Having displayed the object type, object name, time of last change, and time of last access, DIR2 then reads the next directory entry and re-enters the DOWHILE processing loop. After all directory entries have been processed, DIR2 closes the directory using the closedir API and returns.


With DIR2, you can now access, from a CL program, the entries associated with an IFS directory and determine when each file was last accessed along with when the contents of each file were last changed. A good starting point for assisting you in managing your IFS! Next month, we'll continue to look at what you can learn about the contents of your IFS directories.


As readers like Emmanuel W. have pointed out, the information accessed is also available without the use of APIs. The i operating system provides CL commands such as Retrieve Directory Information (RTVDIRINF)  and Print Directory Information (PRTDIRINF), which, through the use of database files, can also provide a wealth of information concerning the objects found in the IFS. The RTVDIRINF-generated files, however, utilize various attributes (such as Graphic and Timestamp fields) that are often easiest used with high-level languages such as RPG or COBOL rather than CL. There is no doubt, though, that there is more than one way to manage your IFS, with the approach shown here, using system APIs, being just one of the possible solutions available to you. 

The Entire Structure Returned by the stat API

Note: Two fields (&RDev64 and &Dev64) in the following definition of &FileInfo are defined as 8-byte character fields. These fields are actually 8-byte unsigned integers, but CL does not provide support for unsigned integers greater than 4 bytes in length until V7R1. With V7R1, you should define these two fields as TYPE(*UINT) with LEN(8).


Dcl        Var(&FileInfo)   Type(*Char) Len(128)       

Dcl        Var(&Mode)       Type(*UInt) +              

             Stg(*Defined) DefVar(&FileInfo 1)                      

Dcl        Var(&FileID)     Type(*UInt) +                           

             Stg(*Defined) DefVar(&FileInfo 5)                      

Dcl        Var(&NbrLinks)   Type(*UInt) Len(2) +                    

             Stg(*Defined) DefVar(&FileInfo 9)                      

Dcl        Var(&Rsv1)       Type(*UInt) Len(2) +                    

             Stg(*Defined) DefVar(&FileInfo 11)                     

Dcl        Var(&UID)        Type(*UInt) +                            

             Stg(*Defined) DefVar(&FileInfo 13)                     

Dcl        Var(&GID)        Type(*UInt) +                           

             Stg(*Defined) DefVar(&FileInfo 17)                     

Dcl        Var(&Size)       Type(*Int) +                            

             Stg(*Defined) DefVar(&FileInfo 21)                     

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(&LstStsChg)  Type(*Int) +                              

             Stg(*Defined) DefVar(&FileInfo 33)                       

Dcl        Var(&FSID)       Type(*UInt) +                             

             Stg(*Defined) DefVar(&FileInfo 37)                       

Dcl        Var(&BlkSiz)     Type(*UInt) +                             

             Stg(*Defined) DefVar(&FileInfo 41)                       

Dcl        Var(&AlcSiz)     Type(*UInt) +                             

             Stg(*Defined) DefVar(&FileInfo 45)                       

Dcl        Var(&ObjTyp)     Type(*Char) Len(10) +                      

             Stg(*Defined) DefVar(&FileInfo 49)                       

Dcl        Var(&ObjTypNul)  Type(*Char) Len(1) +                      

             Stg(*Defined) DefVar(&FileInfo 59)                       

Dcl        Var(&CP)         Type(*UInt) Len(2) +                      

             Stg(*Defined) DefVar(&FileInfo 60)                       

Dcl        Var(&CCSID)      Type(*UInt) Len(2) +        

             Stg(*Defined) DefVar(&FileInfo 62)         

Dcl        Var(&RDev)       Type(*UInt) +               

             Stg(*Defined) DefVar(&FileInfo 64)         

Dcl        Var(&NLink32)    Type(*UInt) +               

             Stg(*Defined) DefVar(&FileInfo 68)         

Dcl        Var(&RDev64)     Type(*Char) Len(8) +        

             Stg(*Defined) DefVar(&FileInfo 72)         

Dcl        Var(&Dev64)      Type(*Char) Len(8) +        

             Stg(*Defined) DefVar(&FileInfo 80)         

Dcl        Var(&VFS)        Type(*UInt) +               

             Stg(*Defined) DefVar(&FileInfo 88)         

Dcl        Var(&Rsv2)       Type(*Char) Len(32) +       

             Stg(*Defined) DefVar(&FileInfo 92)         

Dcl        Var(&GenID)      Type(*UInt) +               

             Stg(*Defined) DefVar(&FileInfo 124)        

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,