Error Handling When Running Commands from RPG

Article ID: 56877

RPG programmers often need to run a CL command, such as OVRDBF, CLRPFM, or CPYF, in the middle of an RPG program. There are three basic ways to do this. In this article, I demonstrate each method and talk about the pros and cons of each. I also offer in-depth information about how to handle errors with each method. Because error handling is one of the biggest differences between the methods, I think it deserves some extra attention.

The Execute Commands (QCMDEXC) API

Perhaps the simplest and most commonly used way to run a command is the QCMDEXC API. This API accepts three parameters, though the last one is optional.

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)
  • cmd: the command string to run; the API can accept any size variable (1–32,702 bytes long) for this parameter
  • len: the length of the variable you provided for the first parameter
  • igc: an optional parameter; you can pass 'IGC' (in all uppercase) for this parameter to tell the system to process Double-Byte Character Set (DBCS) characters

QCMDEXC is easy to use; just pass the command string in the first parameter, and the length in the second.

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

      /free          
       QCMDEXC( 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)' : 33);

QCMDEXC automatically trims trailing spaces from your command string, freeing you from trimming them yourself. For example, in the following code, mycmd ends in 2,767 trailing blanks, but QCMDEXC strips them:

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

     D mycmd           s           3000a

      /free          
       mycmd = 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)';
       QCMDEXC( mycmd: %size(mycmd) );

Although you could use the %trim() BIF to trim the spaces, it would provide no advantage. QCMDEXC can trim them as quickly as %trim() can--perhaps even more quickly. On the other hand, if you use a VARYING string, no blanks are added to begin with, and therefore they don't need to be trimmed--resulting in faster performance, especially for large fields:

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

     D mycmd           s          32702a   varying

      /free          
       mycmd = 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)';
       QCMDEXC( mycmd: %len(mycmd) );

All the APIs that I discuss in this article (including QCMDEXC) run commands at the same call-stack level as the caller. This behavior is useful for commands such as overrides that are sensitive to the call-stack level that they're run in. It's also useful from the perspective that any errors that occur are sent directly to your program, and they can therefore be monitored for.

QCMDEXC Error Handling

When a command run via QCMDEXC fails, an *ESCAPE message is sent to your program and logged to the job log. You can use traditional RPG methods to catch the error and see what the problem is:

     D PSDS           SDS                  qualified
     D  MsgId                         7A   overlay(PSDS:40)

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

     D cmd             s            100a   varying
     D errorMsg        s             80a   varying

      /free

           monitor;
             cmd = 'CPYF FROMFILE(CUSTMAS) +
                         TOFILE(QTEMP/CUSTCOPY) +
                         TOMBR(*FROMMBR) +
                         MBROPT(*ADD) +
                         CRTFILE(*YES)';
             QCMDEXC( cmd : %len(cmd));
           on-error;
             ErrorMsg = 'Copy of CUSTMAS failed with '
                      + PSDS.MsgId;
             // ...now log error, or show to user,
             //     or whatever is appropriate.
           endmon;

Because the details are logged to the job log, you can use the QMHRCVPM API to retrieve all the message details in their full glory and get a lot more information than the MsgId I demonstrated. You also get the MsgDta, information about the message file, Coded Character Set Identifier (CCSID) information, and much more. Basically, you can retrieve anything you want to know about the error message this way, albeit at the cost of making the code a bit more complex.

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

     D QMHRCVPM        PR                  extpgm('QMHRCVPM')
     D   RcvVar                   32767A   options(*varsize)
     D   RcvVarLen                   10I 0 const
     D   Format                       8A   const
     D   StackEnt                    10A   const
     D   StackCount                  10I 0 const
     D   MsgType                     10A   const
     D   MsgKey                       4A   const
     D   WaitTime                    10I 0 const
     D   Action                      10A   const
     D   ErrorCode                32767A   options(*varsize)

     D ErrorCode       ds                  qualified
     D    BytesProv                  10I 0 inz(0)
     D    BytesAvail                 10I 0 inz(0)

     D MsgInfo         ds                  qualified
     D   BytesRtn                    10i 0
     D   BytesAvail                  10i 0
     D   MsgSev                      10i 0
     D   MsgId                        7a
     D   MsgType                      2a
     D   MsgKey                       4a
     D   Msgf                        10a
     D   MsgfReqLib                  10a
     D   Msgflib                     10a
     D   SendJob                     26a
     D   SendPgm                     12a
     D   SendPgmInst                  4a
     D   DateSent                     7a
     D   TimeSent                     6a
     D   RcvPgm                      10a
     D   RcvPgmInst                   4a
     D   SendType                     1a
     D   RcvType                      1a
     D                                1a
     D   CcsidStsTxt                 10i 0
     D   CcsidStsDta                 10i 0
     D   Alert                        9a
     D   CCSIDmsg                    10i 0
     D   CCSIDdta                    10i 0
     D   MsgDtaLen                   10i 0
     D   MsgDtaAvail                 10i 0
     D   MsgLen                      10i 0
     D   MsgAvail                    10i 0
     D   MsgHlpLen                   10i 0
     D   MsgHlpAvail                 10i 0
     D   MsgDta                   32767a

     D cmd             s            100a   varying
     D errorMsg        s             80a   varying

      /free

           monitor;
             cmd = 'CPYF FROMFILE(CUSTMAS) +
                         TOFILE(QTEMP/CUSTCOPY) +
                         TOMBR(*FROMMBR) +
                         MBROPT(*ADD) +
                         CRTFILE(*YES)';
             QCMDEXC( cmd : %len(cmd));
           on-error;
             QMHRCVPM( MsgInfo
                     : %size(MsgInfo)
                     : 'RCVM0200'
                     : '*'
                     : 0
                     : '*LAST'
                     : *blanks
                     : -1
                     : '*OLD'
                     : ErrorCode );

             // Now the MsgInfo structure is filled in
             // with loads of information about the
             // error...

           endmon;

The System() API

One of the chief frustrations people encounter with QCMDEXC is having to calculate and pass the length in the second parameter. To me, passing a length parameter doesn't seem like a big deal, but others prefer to avoid it. The system() API does exactly that, and it works much like QCMDEXC, except it calculates the length for you.

The system() API is part of the ILE C/C++ runtime, which means that you have to bind to the QC2LE binding directory to use it, and you can call it only as an ILE procedure, so you must be using an ILE program--that is, a program compiled with DFTACTGRP(*NO)--in order to use it.

     H DFTACTGRP(*NO) BNDDIR('QC2LE')                            
                                                                 
     D system          PR            10I 0 extproc('system')     
     D   command                       *   value options(*string)

There's only one parameter--the command to execute--so it's pretty self-explanatory.

     H DFTACTGRP(*NO) BNDDIR('QC2LE')

     D system          PR            10I 0 extproc('system')
     D   command                       *   value options(*string)

      /free

          system('CLRPFM QTEMP/MYFILE');

System() Error Handling

Unlike QCMDEXC, the system() API returns no escape message to your program if the command that it called fails. Instead, you have to check the return value. If the return value is 0, the command succeeded; otherwise, something went wrong. You can import the _EXCP_MSGID variable from the ILE C/C++ runtime to get the MsgId of the failure.

     H DFTACTGRP(*NO) BNDDIR('QC2LE')

     D system          PR            10I 0 extproc('system')
     D   command                       *   value options(*string)

     D ErrMsgID        S              7A   Import('_EXCP_MSGID')
     D rc              S             10i 0
     D ErrMsg          S             80a   varying

      /free

          rc = system('CLRPFM QTEMP/MYFILE');

          select;
          when rc = 0;
            // success!
          when ErrMsgId = 'CPF3142';
            system('CRTPF QTEMP/MYFILE RCDLEN(1000)');
          other;
            ErrMsg = 'Unable to clear flat file.  Error ' + ErrMsgId;
            //  show error to user, or write to log, or...
          endsl;

          *inlr = *on;

      /end-free

Error messages sent to commands that are run via system() don't get logged to the job log, and this is my biggest complaint about the system() API. If something goes wrong, there's no way to get the complete error message information. Oftentimes, the MsgId is ambiguous, relying on the MsgDta to provide more information, but you can't get the MsgDta when using system(), and this makes it inflexible for handling errors.

I don't recommend the system() API, because its error handling is poor. Providing the length to QCMDEXC is a small price to pay for having proper diagnostic information.

The Process Commands (QCAPCMD) API

The last API, QCAPCMD, is an extremely deluxe and versatile API. It can work just like QCMDEXC, but it can also do more, including prompting, syntax checking, S/36 commands, and more.

The major drawback of QCAPCMD is its complexity. It requires a great deal more effort to use than QCMDEXC because it has more options and therefore more parameters.

     D QCAPCMD         PR                  ExtPgm('QCAPCMD')
     D   command                  32702a   options(*varsize) const
     D   cmdlen                      10i 0 const
     D   optblk                            like(CPOP0100_t) const
     D   optblklen                   10i 0 const
     D   optblkfmt                    8a   const
     D   chgcmd                   32702a   options(*varsize)
     D   chglenprv                   10i 0 const
     D   chglenrtn                   10i 0
     D   errorcode                32767a   options(*varsize)

     D CPOP0100_t      ds                  qualified
     D   type                        10i 0 inz(0)
     D   dbcs                         1a   inz('0')
     D   prompter                     1a   inz('0')
     D   syntax                       1a   inz('0')
     D   msgkey                       4a   inz(x'00000000')
     D   ccsid                       10i 0 inz(0)
     D                                5a   inz(x'0000000000')

     D ErrorCode       ds                  qualified
     D   bytesprov                   10i 0 inz(%size(ErrorCode))
     D   bytesavail                  10i 0 inz(0)
     D   msgid                        7a
     D                                1a
     D   msgdta                   32759a

     D optblk          ds                  likeds(CPOP0100_t)
     D                                     inz(*likeds)
     D myCmd           s            500a   varying
     D NotUsed         s              1a
     D NotUsedLen      s             10i 0
      /free
        myCmd = 'ALCOBJ +
                   OBJ((CUSTMAS *FILE *EXCL *FIRST)) +
                   WAIT(1)';

        QCAPCMD( myCmd
               : %len(myCmd)
               : optblk
               : %size(optblk)
               : 'CPOP0100'
               : NotUsed
               : 0
               : NotUsedLen
               : ErrorCode );

To keep this article relatively short and simple, I'm deliberately glossing over QCAPCMD's various options and parameters. For details about all its options, please see the Information Center:
http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/apis/qcapcmd.htm

Suffice it to say that with the options I've given, QCAPCMD will work the same way as QCMDEXC, except that error information is returned in the ErrorCode data structure.

QCAPCMD Error Handling

QCAPCMD returns error information in the standard API error code data structure. This means you can easily get the MsgId and MsgDta in your program. Just examine the ErrorCode data structure.

        select;
        when ErrorCode.bytesAvail = 0;
           // no errors;
        when ErrorCode.MsgId = 'CPF0939';
           // object not found
        when ErrorCode.MsgId = 'CPF0952';
           // library not found
        when ErrorCode.MsgId = 'CPF1002';
            // unable to allocate
        other;
            // unexpected errror. Now what?
        endsl;

If you prefer to have QCAPCMD send an *ESCAPE message, you can set the bytes provided to zero, and QCAPCMD sends an *ESCAPE message to the job log just like QCMDEXC does.

     D ErrorCode       ds                  qualified
     D   bytesprov                   10i 0 inz(0)
     D   bytesavail                  10i 0 inz(0)
     D   msgid                        7a
     D                                1a
     D   msgdta                   32759a

Now you can check the Program Status Data Structure (PSDS) or call QMHRCVPM, just as you would have done with QCMDEXC.

Error Handling Comparison

In most of my software, I have errors that I want to handle and errors that I'd prefer to propagate to my caller.

     D QCMDEXC         PR                  ExtPgm('QCMDEXC')
     D   cmd                      32702a   const options(*varsize)
     D   len                         15p 5 const
     D   igc                          3a   const options(*nopass)

     D QMHRSNEM        PR                  ExtPgm('QMHRSNEM')
     D   MsgKey                       4A   const
     D   ErrorCode                32767A   options(*varsize)

     D EscapeErr       ds                  qualified
     D   bytesProv                   10i 0 inz(0)
     D   bytesAvail                  10i 0 inz(0)

     D PSDS           SDS                  qualified
     D  MsgId                         7A   overlay(PSDS:40)
     *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      * util_LockObj():  Gain exclusive use of an object on disk
      *
      *     Obj = (input) object name
      *     Lib = (input) library containing object
      *    Type = (input) object type (*FILE, *DTAARA, etc)
      *  Member = (input/optional) name of member (if this
      *                      is a file) defaults to *FIRST
      *
      * Returns 0 if successful, -1 if object is in use
      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     P util_LockObj    B                   export
     D util_LockObj    PI            10i 0
     D   Obj                         10a   const
     D   Lib                         10a   const
     D   Type                        10a   const
     D   Member                      10a   const options(*nopass:*omit)

     D Mbr             s                   like(Member)

     D cmd             s            100a   varying
     D err             s              7a

      /free

           if ( %parms >= 4 and %addr(Member) <> *null );
              Mbr = Member;
           elseif type='*FILE';
              Mbr = '*FIRST';
           endif;

           monitor;
              cmd = 'ALCOBJ OBJ((' + %trim(Lib) + '/' + %trim(Obj)
                                   + ' ' + %trim(Type)
                                   + ' *EXCL'
                                   + ' ' + %trim(Mbr) + ')) +
                            WAIT(1)';
              QCMDEXC( cmd : %len(cmd));
              err = *blanks;
           on-error;
              err = PSDS.MsgId;
           endmon;

           if err = 'CPF1002';  // Object in use
              return -1;
           elseif err <> *blanks;
              QMHRSNEM( *blanks: EscapeErr );
              return -1;
           else;
              return 0;
           endif;

      /end-free
     P                 E

In this example, I've written a subprocedure to call for the ALCOBJ command, because no equivalent API exists. If the objects are in use, I want to handle that as a "normal condition." However, if any other error occurs, I want to send that back to my caller, forcing the caller to use a MONITOR or *PSSR to catch it. The QMHRSNEM API resends the last *ESCAPE message in my job log to my caller. I find that almost always when I'm running commands from my RPG programs, I want to do something like this. That is, I want to "handle" certain errors and let any other error get sent back to my caller. Here are some things to be aware of for this type of handling:

System(): Doesn't work for this type of logic. It's incapable of propagating errors, because not all the error information is available.

QCMDEXC(): Works well, but if you need the MsgDta, you have to use the QMHRCVPM API to get it.

QCAPCMD(): Works well and provides the MsgDta. But, how do you propagate errors? You can't simply resend the error message from the API error structure, because the structure doesn't have message file information. You can set the bytes provided field of the ErrorCode to 0, and get an *ESCAPE message just like QCMDEXC--but then why have you gone to all the additional effort of using QCAPCMD?

My conclusion is usually to use QCMDEXC. I never use system(), because, even though it calculates the length of the command for me, it provides inadequate diagnostics. Although QCAPCMD provides tons of diagnostics and more options than QCMDEXC, I rarely find a use for them. I end up using QCAPCMD the same way I'd have used QCMDEXC, so why not use the simpler API?

Thanks, Randal.

I did think about including CLLE modules in my list of options but decided against it. CLLE modules are especially useful when working with commands that retrieve data (since these cannot be executed from QCMDEXC, system(), or QCAPCMD). For example, the RTVMBRD command has lots of information that's difficult to get in RPG without calling complex APIs. It's a great candidate for a CLLE module!

However, I decided that this topic would be better served by its own article, instead of including it in this one. As you said, it can be a lot of work for a single command; therefore, I think CLLE modules usually serve a little bit different purpose than in-line calls to the APIs and therefore should be discussed separately.

There is a fourth option—CLLE modules. Create prototypes for the command(s) and place them in a service program. I admit this is a lot of work for a single command, but with multiple commands I find it beneficial. Why do you need CL programs? Isn't this the whole idea of ILE?

ProVIP Sponsors

ProVIP Sponsors