Searching for files and objects in the IFS

findfilesseuThe code examples in this article is based on the first version of the FindFiles() procedure. The FindFiles() procedure has been updated to Totally Free RPG and a bug have been fixed. You will find the source code, example and build script here.

I am working on a utility and I wanted to add the ability to search for files in the IFS. That is, I wanted my utility to search the entire or relevant parts of the tree structure (including sub-directories) seen from the IFS’s point of view. Had it just been a search for objects in the libraries, a DSPOBJD command could do the job. But my ambition was that the search should work anywhere in the IFS and, of cause, only find objects matching a name mask that I would provide. Sounds like a reasonable requirement, don’t you think?

After a search on Google, I came up empty handed. That is, I found a lot of examples on how to read one directory in the IFS and process the files found, but I did not find a generic file/object search function that was able to search into sub-directories. Further, the examples I found, the programming of reading a directory was mixed with what one wanted to do with the files/objects found. I wanted a more clean and simple solution. One thing became clear, I had write my own file/object search function 🙂

What I came up with, is the ILE RPG function called FindFiles(). FindFiles() is very easy to use and it is quite powerful. FindFiles() can search a path for files/objects that matches a file mask. Further, it can search sub-directories/folders as well. It can search in the entire IFS, that is, it works on Unix like folders as well as good old QSYS libraries.

To start a search for files/objects, you must call the FindFiles() function with the parameters:

FindFiles(starting_path: mask: search_subdirs: files_found_procedure_pointer)

wrklnkWhere the parameters are:

      • starting_path
        The path to start the search. This can be a IFS or QSYS path. FindFiles() works on both.
      • mask
        The mask which found files must match. You can pass these values to return all files/objects:
        ‘*.*’
        ‘*’
        ‘ ‘
        You can enter the mask in upper or lower case. FindFiles() is case insensitive. If you enter ‘*abc*.*’ the files ‘ABC.TXT’, ‘ABCDEFG.TXT’, ‘AbCdEfG.tXt’ are found.
      • search_subdirs:
        Indicator for search of sub-directories:
        *On  – Sub-directories will be searched too.
        *Off – Only the directory in starting_path will be searched.
      • pointer
        This must be a procedure pointer to a procedure you write that will be called for each file/directory found.

FindFiles() returns an indicator:

  • *On
    The search was terminated as the return value of the user supplied FileFound() function was set to *On.
  • *Off
    The search terminated successful. Well, maybe there was reported errors to FileFound() but the function returned *Off.

The procedure pointer must point to a function you have written yourself. It must have the following interface:

     d FileFound       Pr             1n
     d   iPath                      250a   Value
     d   iFileName                  100a   Value
     d   iIsDir                       1n   Value
     d   iStatDS                           Value Like(StatDS)
     d   iErrNo                      10i 0 Value

In your program you can call it what ever you like, as long as the parameter structure is identical.

The parameters are:

  • iPath
    The path in which the file/object was found. E.g. ‘/home/jesper/’, ‘/QSYS.LIB/QGPL.LIB/’, ‘/ExternalFiles/ToCustomers/Invoices/’.
  • iFileName
    This is the name of the file/object found.
  • iIsDir
    This in an indicator that tells if the iFileName is a directory (*On) or a file/object (*Off). This is especially handy as physical and logical files in the QSYS file system are seen as directories, however device files are seen as files/objects.
  • iStatDS
    This is StatDS structure containing information about the file/object. Before you can use the values, you must copy iStatDS to a datastructure of the type StatDS. This datastructure is included in the example program.
  • iErrNo
    If an error occurs, the error code returned by the API in question is returned in this field. The search for further files/objects will continue. See the example program for how to handle this.

What happens is, that for each file found your FileFound() function is called. Your FileFound() function then do what needs to be done with the file found.

This is an example of a FileFound() function:

      *
      * FileFound
      * ---------
      * This function is a 'callback' function. It is called every time
      * a file is found that matches the search mask.
      *
      * Please note, that for V4R5 and before, you must declare this
      * function with keyword Export. This is not needed for V5R1 and
      * later. Also in V4R5 and below, you must pass the name in 
      * singlequotes and in uppercase to the BIF %PAddr().
      *
     P FileFound       B                   Export
     d FileFound       Pi             1n
     d   iPath                      250a   Value
     d   iFileName                  100a   Value
     d   iIsDir                       1n   Value
     d   iStatDS                           Value Like(StatDS)
     d   iErrNo                      10i 0 Value
      *
      * Prototype for API that returns the text for an error number.
      *
     d StrError        Pr              *   ExtProc('strerror')
     d   ErrNum                      10i 0 Value
      *
      * Stat data structure parsed on the call. For the meaning of each
      * field, seach the net :-)
      *
     d StatDS          Ds           128
       ...
       ...
     d p_errmsg        s               *
     d ReturnCode      s              1n   Inz(*Off)
      *
      * If an error occurred, then display that error.
      *
     c                   If        iErrNo <> *Zeros
     c                   EVal      p_errmsg = StrError(iErrNo)
     c                   EVal      Msg = 'Error reading ' +%TrimR(iPath) +
     c                                   ': ' + %Str(p_errmsg)
     c     Msg           Dsply
      *
      * Signal that the search must stop.
      *
     c                   EVal      ReturnCode = *On
     c                   Else
      *
      * In this example, we ignore the parent dir.
      *
     c                   If        iFileName <> '..'
      *
     c                   If        iIsDir = *On
     c                   EVal      Msg = 'Dir.:'
     c                   Else
     c                   EVal      Msg = 'File:'
     c                   EndIf
      *
     c                   EVal      Msg = %TrimR(Msg) + ' '+ %TrimR(iPath) +
     c                                   %TrimR(iFileName)
     c     Msg           Dsply
      *
      * As iStatDS is merly a string field, we must convert to StatDS
      * structure to get each field.
      *
     c                   EVal      StatDS = iStatDS
      *
      * Call convert routine to get a 'good old timestamp' of when the
      * file was last modified.
      *
     c                   EVal      Stamp = CnvUTimeToTS(st_mtime)
     c                   EVal      Msg = 'Last modified: ' + %Char(Stamp)
      *
     c     Msg           Dsply
      *
     c                   EndIf
     c                   EndIf
      *
     c                   Return    ReturnCode
      *
     P FileFound       E

Using the FileFound() function gives a more clean implementation, because FileFound() is not cluttered with code for traversing the IFS tree structure, checking file-/object names for a match to the mask etc.

You can find the sources, as well as an example program, here. I have included an installation package that will copy the four source members to your IBM i. Read more about this on the page. As the APIs that FindFiles() calls returns file/object time and date in Unix format, I have included a function to convert from the Unix format to a TimeStamp. The function is called CnvUTimeToTS() and is listed in the example program.

So, now you can search for files and objects too 🙂

If you wonder why the source listings shows fixed format ILE RPG and I have mentioned OS/400 V4R5, well it is because my personal AS/400 (now IBM i) is at that level 🙂 And, yes, I know this is year 2016 😉

Leave a Reply