24
Tue, Dec
1 New Articles

The CL Corner: Trying to Get a Handle on Your IFS?

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

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.

 

Pgm        Parm(&Dir_In)                               

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

             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(&MsgTxt)     Type(*Char) Len(300)     

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

             Value(x'00')                                

Dcl        Var(&Null_Ptr)   Type(*Ptr)                   

                                                          

ChgVar     Var(&Dir) Value(&Dir_In *TCat &Null)          

MonMsg     MsgID(MCH3601) Exec(Do)                       

           RcvMsg MsgType(*Last)                         

           ChgVar Var(&Dir) Value('.' *TCat &Null)

           EndDo                                         

                                                         

CallPrc    Prc('opendir') Parm((&Dir)) 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)             

           ChgVar Var(&MsgTxt) +                       

             Value(%sst(&Name 1 &LenOfName))           

           SndPgmMsg Msg(&MsgTxt) ToPgmQ(*Ext)         

           CallPrc Prc('readdir') +                    

             Parm((&Dir_Ptr *ByVal)) RtnVal(&DirEnt_Ptr)

           EndDo                                       

                                                       

CallPrc    Prc('closedir') Parm((&Dir_Ptr *ByVal))            

                                                       

EndPgm                                                 

 

To compile the DIR program, you can use this command:

 

CRTBNDCL PGM(DIR)

 

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:

 

.    

..   

File1

File2

 

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:

 

CRTDIR DIR('MyImbeddedDir')

 

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:

 

.           

..          

File1       

File2       

MyImbeddedDir

 

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?

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, 

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

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: