16
Tue, Apr
6 New Articles

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

CL
Typography
  • 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) +          

             Value(x'00')                                 

Dcl        Var(&Null_Ptr)   Type(*Ptr)                     

                                                          

ChgVar     Var(&InlPath) Value(&Dir_In)                   

MonMsg     MsgID(MCH3601) Exec(Do)                        

           RcvMsg MsgType(*Last)                           

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

           EndDo                                          

                                                          

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

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

                  

EndPgm            

 

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:

 

CRTBNDCL PGM(DIR2)

 

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

 

CRTCLMOD MODULE(DIR2)

CRTPGM PGM(DIR2) BNDDIR(QC2LE)

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,

 

Bruce Vining

Bruce Vining is president and co-founder of Bruce Vining Services, LLC, a firm providing contract programming and consulting services to the System i community. He began his career in 1979 as an IBM Systems Engineer in St. Louis, Missouri, and then transferred to Rochester, Minnesota, in 1985, where he continues to reside. From 1992 until leaving IBM in 2007, Bruce was a member of the System Design Control Group responsible for OS/400 and i5/OS areas such as System APIs, Globalization, and Software Serviceability. He is also the designer of Control Language for Files (CLF).A frequent speaker and writer, Bruce can be reached at This email address is being protected from spambots. You need JavaScript enabled to view it.. 


MC Press books written by Bruce Vining available now on the MC Press Bookstore.

IBM System i APIs at Work IBM System i APIs at Work
Leverage the power of APIs with this definitive resource.
List Price $89.95

Now On Sale

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$0.00 Raised:
$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: