7.9. Example of making a DIR command for QSHELL

Here's a slightly more complex example of reading a directory. This program is designed to be run under the QShell utility that IBM provides.

QShell is intended to emulate a UNIX command prompt, and therefore it provides the UNIX "ls" command to view directories. But, what if you like the way directories look in the MS-DOS "dir" command? Well... we could write out own DIR command using the APIs, right? Let's try!

One of the interesting things about QSHELL, is that it provides us with 3 stream file descriptors, already opened, which we can use for input and output to the shell.

Descriptor #0 is referred to as "standard input", but we will not use it in our example.

Descriptor #1 is referred to as "standard output". It's the place to write our directory listing to. We should treat it like a text file, which is to say that we start a new line by sending a CRLF sequence.

Descriptor #2 is referred to as "standard error". Any error messages that we need to communicate to the user should be sent there.

There's a lot more that I could explain about QSHELL, but alas, it's a bit beyond the scope of this document to fully explain this environment...

We'll also need to send text to the screen listing whether or not a file is a directory, and also the file size, because these are done in MS-DOS. Fortunately, we can use the stat() API to retrieve these.

Finally, we'll need to report the date that's associated with each file in the directory. We can get that from the stat() API as well, but all the dates in stat() are listed in Universal Time Coordinated (UTC) and not in our time zone. The dates that appear, are actually listed as a number of seconds since the "epoch" of January 1st, 1970. So, to make all of this behave as we want it to, we've got our work cut out for us. Still, hopefully when you see the code, you'll understand what's going on!

Here's the code:

      * CH7QSHDIR: More complex example of reading a dir in the IFS
      *  We try to make our output look like the MS-DOS "DIR" command
      *  (From Chap 7)
      *
      * To compile:
      *   CRTBNDRPG CH7QSHDIR SRCFILE(xxx/QRPGLESRC) DBGVIEW(*LIST)
      *
     H DFTACTGRP(*NO) ACTGRP(*NEW) BNDDIR('QC2LE') BNDDIR('IFSTEXT')

     D/copy IFSEBOOK/QRPGLESRC,IFSIO_H
     D/copy IFSEBOOK/QRPGLESRC,ERRNO_H
     D/copy IFSEBOOK/QRPGLESRC,IFSTEXT_H

     D STDIN           C                   CONST(0)
     D STDOUT          C                   CONST(1)
     D STDERR          C                   CONST(2)

     D S_ISDIR         PR             1N
     D   mode                        10U 0 value

     D CEEUTCO         PR                  ExtProc('CEEUTCO')
     D   hours                       10I 0
     D   minutes                     10I 0
     D   seconds                      8F

     D line            s           1024A
     D len             s             10I 0
     D dir             S               *
     D filename        S           1024A   varying
     D fnwithdir       S           2048A   varying
     D mystat          S                   like(statds)
     D curr            s           1024A
     D curdir          s           1024A   varying
     D dot             S             10I 0
     D notdot          S             10I 0
     D epoch           S               Z   inz(z'1970-01-01-00.00.00.000000')
     D ext             S              3A
     D filedate        S              8A
     D filetime        S              6A
     D modtime         S               Z
     D mydate          S               D
     D mytime          S               T
     D shortfn         S              8A
     D size            S             13A
     D worktime        S              8A
     D hours_utc       s             10I 0
     D mins_utc        s             10I 0
     D secs_utc        s              8F
     D utcoffset       s             10I 0

      * Here's an example of what an MS-DOS directory listing
      * looks like:
      *
      *   Directory of C:\WINDOWS
      *
      *  .              <DIR>        10-24-00 10:28a .
      *  ..             <DIR>        10-24-00 10:28a ..
      *  COMMAND        <DIR>        05-08-00 11:54a COMMAND
      *  VB       INI         1,245  10-04-01  9:12p VB.INI
      *  LOCALS~1       <DIR>        10-06-01  9:44p Local Settings
      *  BDDKL    DRV         1,986  11-21-01  8:43p bddkl.drv
      *  BPKPC    DRV         3,234  11-21-01  8:43p bpkpc.drv
      *  PILCIKK  DRV         1,122  11-21-01  8:43p pilcikk.drv
      *  NEWRES~1 RC          1,440  12-12-01  2:02a newrestest.rc

     c                   eval      *inlr = *on

     C*******************************************
     C* get the number of seconds between local
     C* time an Universal Time Coordinated (UTC)
     C*******************************************
     c                   callp(e)  CEEUTCO(hours_utc: mins_utc: secs_utc)
     c                   if        %error
     c                   eval      utcoffset = 0
     c                   else
     c                   eval      utcoffset = secs_utc
     c                   endif

     C*******************************************
     C* Use the getcwd() API to find out the
     C* name of the current directory:
     C*******************************************
     c                   if        getcwd(%addr(curr): %size(curr)) = *NULL
     c                   eval      line = 'getcwd(): ' +
     c                                     %str(strerror(errno))
     c                   eval      len = %len(%trimr(line))
     c                   callp     writeline(STDERR: %addr(line): len)
     c                   return
     c                   endif

     c                   eval      curdir = %str(%addr(curr))

     C*******************************************
     C* open the current directory:
     C*******************************************
     c                   eval      dir = opendir(curdir)
     c                   if        dir = *NULL
     c                   eval      line = 'opendir(): ' +
     c                                     %str(strerror(errno))
     c                   eval      len = %len(%trimr(line))
     c                   callp     writeline(STDERR: %addr(line): len)
     c                   return
     c                   endif

     c                   eval      line = ''
     c                   eval      len = 0
     c                   callp     writeline(STDOUT: %addr(line): len)

     c                   eval      line = ' Directory of ' + curdir
     c                   eval      len = %len(%trimr(line))
     c                   callp     writeline(STDOUT: %addr(line): len)

     c                   eval      line = ''
     c                   eval      len = 0
     c                   callp     writeline(STDOUT: %addr(line): len)

     c                   eval      p_statds = %addr(mystat)

     c                   eval      p_dirent = readdir(dir)
     c                   dow       p_dirent <> *NULL
     c                   eval      filename = %subst(d_name:1:d_namelen)
     c                   eval      fnwithdir = curdir + '/' + filename
     c                   if        stat(fnwithdir: %addr(mystat))=0
     c                   exsr      PrintFile
     c                   endif
     c                   eval      p_dirent = readdir(dir)
     c                   enddo

     c                   callp     closedir(dir)


     C*===============================================================
     C* For each file in the directory, print a line of info:
     C*===============================================================
     CSR   PrintFile     begsr
     C*------------------------
     C* Separate into extension & short filename:
     c     '.'           check     filename      notdot
     c                   if        notdot = 0
     c                   eval      ext = *blanks
     c                   eval      shortfn = filename
     c                   else
     c                   eval      dot = %scan('.': filename: notdot)
     c                   if        dot > 0
     c                   eval      ext = %subst(filename:dot+1)
     c                   eval      shortfn = %subst(filename: 1: dot-1)
     c                   else
     c                   eval      ext = *blanks
     c                   eval      shortfn = filename
     c                   endif
     c                   endif

     C* Show size if this is not a directory:
     c                   if        S_ISDIR(st_mode)
     c                   eval      size = '<DIR>'
     c                   else
     c                   eval      size = %editc(st_size: 'K')
     c                   endif

     C* figure out date & time:
     c     epoch         adddur    st_atime:*S   modtime
     c                   adddur    utcoffset:*S  modtime
     c                   move      modtime       mydate
     c                   move      modtime       mytime
     c     *MDY-         move      mydate        filedate
     c     *USA          move      mytime        worktime
     c                   eval      filetime=%subst(worktime:1:5) +
     c                                      %subst(worktime:7:1)

     C* and write it to QSH STDOUT:
     c                   eval      line = shortfn + ' ' + ext + '  ' +
     c                               size + '  ' + filedate + ' ' +
     c                               filetime + ' ' + filename
     c                   eval      len = %len(%trimr(line))
     c                   callp     writeline(STDOUT: %addr(line): len)
     C*------------------------
     CSR                 endsr


      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      *  This tests a file mode to see if a file is a directory.
      *
      * Here is the C code we're trying to duplicate:
      *      #define _S_IFDIR    0040000                                       */
      *      #define S_ISDIR(mode) (((mode) & 0370000) == _S_IFDIR)
      *
      * 1) ((mode) & 0370000) takes the file's mode and performs a
      *      bitwise AND with the octal constant 0370000.  In binary,
      *      that constant looks like: 00000000000000011111000000000000
      *      The effect of this code is to turn off all bits in the
      *      mode, except those marked with a '1' in the binary bitmask.
      *
      * 2) ((result of #1) == _S_IFDIR)  What this does is compare
      *      the result of step 1, above with the _S_IFDIR, which
      *      is defined to be the octal constant 0040000.  In decimal,
      *      that octal constant is 16384.
      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     P S_ISDIR         B
     D S_ISDIR         PI             1N
     D   mode                        10U 0 value

     D                 DS
     D  dirmode                1      4U 0
     D  byte1                  1      1A
     D  byte2                  2      2A
     D  byte3                  3      3A
     D  byte4                  4      4A

     C* Turn off bits in the mode, as in step (1) above.
     c                   eval      dirmode = mode

     c                   bitoff    x'FF'         byte1
     c                   bitoff    x'FE'         byte2
     c                   bitoff    x'0F'         byte3
     c                   bitoff    x'FF'         byte4

     C* Compare the result to 0040000, and return true or false.
     c                   if        dirmode = 16384
     c                   return    *On
     c                   else
     c                   return    *Off
     c                   endif
     P                 E

      /DEFINE ERRNO_LOAD_PROCEDURE
      /COPY IFSEBOOK/QRPGLESRC,ERRNO_H

You'll need to run this from the QSHELL. If your system doesn't have QSHELL installed, you've got a choice. You can either try to modify my code so that it doesn't need QSHELL, and make it work, or you can install QSHELL. QSHELL should be on your OS/400 CD's, and you should be able to install it free-of-charge.

To run this, do the following:

  1. If you haven't done so already, compile the program according to the instructions at the top of the code.

  2. Start the QSHELL by typing: STRQSH at your OS/400 command prompt.

  3. Run the command by typing: /QSYS.LIB/IFSEBOOK.LIB/CH7QSHDIR.PGM at the QSHELL command prompt.

  4. If that's too much to type, create a symbolic link to the program by typing:

    cd /ifstest

    ln -s /QSYS.LIB/IFSEBOOK.LIB/CH7QSHDIR.PGM dir

    And then adding the /ifstest directory to your path by typing:

    PATH=${PATH}:/ifstest

  5. Now you can just type dir to list the contents of the directory.