Wednesday, October 16, 2013

Capturing QCMDEXC error codes

QCMDEXC is a useful API, that has been around since the launch of the AS400 (IBM i), that is called to execute a single command. It is one of the simpler APIs to call with just two parameters:

  • Command string
  • Command string length

I have seen many examples of RPGLE/RPG IV source code that looks something like this:

01 D CmdString       S            132
02 D CmdLen          S             15  5
03  /free
04     CmdString = 'CLRPFM TESTFILE' ;
05     CmdLength = 15 ;
06  /end-free
07 C                   call(e)    'QCMDEXC'
08 C                   parm                    CmdString
09 C                   parm                    CmdLength
10  /free
11     if (%error) ;

The programmer has gone "out" of RPG/free, line 6, to be able to CALL QCMDEXC, as the CALL operation is not supported in RPG/free.

On line 7 the programmer has used the 'E' operation extender to stop the program erroring if an error was encountered by QCMDEXC, such as TESTFILE not being in the library list or the file is in use by another job. Then they have gone back "into" RPG/free, line 10, before performing a test to see if there is an error, line 11. But what was the error?

By using the Program Status Data Structure (PSDS) it is possible to see the error message code.

You could either code the relevant parts of the PSDS like this:

01 D PgmDs          SDS                  qualified
02 D  ErrCde                40     46
03 D  ErrTxt                91    170

Or if you have read the post Externally described Data Structures you know how to insert an external data structure with all of the PSDS's fields in it like this:

01 D PgmDs         ESDS                  extname(RPG4DS)
02 D                                       qualified

My preference is to use the externally described data structure as the names of the fields in the PSDS are the same across multiple source members, and you have all the other fields there in case you need them at a later date.

Below is an example of how I could do the same as the previous example without going "out" of RPG/free and discovering what the error was:

01 D PgmDs         ESDS                  extname(RPG4DS)
02 D                                       qualified
03 D QCmdExc         PR                  extpgm('QCMDEXC')
04 D                              132    options(*varsize) const
05 D                               15  5 const
06 D CmdString       S            132
07 D ErrorMsg        S              7
08 D ErrorTxt        S             80
    /free
09     CmdString = 'CLRPFM TESTFILE' ;
10     callp(e) QCmdExc(CmdString:%len(CmdString)) ;
11     if (%error) ;
12        ErrorMsg = PgmDs.ExcptTyp + PgmDs.ExcptNbr ;
13        if (ErrorMsg = 'CPF3142') ;  //File not found
14          ErrorTxt = PgmDs.RtvExcptDt ;
           .
           .
15        endif ;
16     endif ;

I have defined QCMDEXC as a procedure, line 3, with the EXTPGM keyword. This indicates that when the procedure is executed the program QCMDEXC is called. When a program called in this manner it cannot return any values.

The 'E' operation extender on the CALLP, line 10, allows me to test if an error was detected on line 11.

By combining the Exception type, PgmDs.ExcptTyp, and Exception number, PgmDs.ExcptNbr, from the PSDS I have the error message code that I can use to condition further processing.

On line 13 I move the Exception text, PgmDs.RtvExcptDt, to another field. I do this just to show that it is possible, I do not think that the text "File TESTFILE in library *LIBL not found" is as helpful to the average user as "The required file could not be found" would be.

Rather than use the CALLP I could use the MONITOR operation code instead:

01   CmdString = 'CLRPFM TESTFILE' ;
02   monitor ;
03     QCmdExc(CmdString:%len(CmdString)) ;
04   on-error ;
05     ErrorMsg = PgmDs.ExcptTyp + PgmDs.ExcptNbr ;
06     if (ErrorMsg = 'CPF3142') ;
07       ErrorTxt = PgmDs.RtvExcptDt ;
           .
           .
08     endif ;
09   endmon ;

For more information about the MONITOR operation code read MONITOR for errors in RPG.

You can learn more about these from the IBM web site:

 

This article was written for IBM i 7.1, and it should work with earlier releases too.

18 comments:

  1. Update: QCMDEXC no longer needs to pass length....Hooray! So it works like the C system function call ...I prefer wrapping the main procedure in a monitor and then using CALLP(e) if I need to handle a specific issue like file not found type thing.

    ReplyDelete
  2. Just to add to Simon, there is a proc "system" that we can use to execute CL commands...the advantage over QCMDEXC is that you do not need to pass the length to this proc - just send the command to be executed and that is it...

    On a separate note - any idea on how to execute CL commands that return parameters through either QCMDEXC or system?
    Example: how to execute a RTVMBRD to get the current record count from within a RPG?

    ReplyDelete
    Replies
    1. I am aware of the SYSTEM( ) function. There will be a post about that in the future.

      I am not sure if there is any API or function that returns values from CL commands called from a RPGLE. I will have a look & if I find something I will make a post about it.

      Delete
  3. The disadvantage of system() over QCMDEXC is that if there is an exception, it removes it from the joblog. You can find out the message ID of that error, but if it was an unexpected error you might need to know the replacement text of the message too. For me the advantage of not having to pass the length is just not worth the problem of losing information about unexpected errors.

    ReplyDelete
  4. Nearly all RTV... CL commands have an equivalent API available to be used within RPGLE.
    In most cases these API's provide more imformation and perform faster than their CL counterparts.
    The RTVMBRD CL command equates to the "Retrieve Member Description (QUSRMBRD)" API

    ReplyDelete
  5. I prefer the QCAPCMD API over QCMDEXC as that has return linkage that includes a return status and details of any error encountered.

    ReplyDelete
  6. I've set up QCAPCMD in a service program as it permits returning more error information. Putting it in a service program permits simplifying the interface, which is not as convenient as QCMDEXC. I've never noticed that the length was difficult to get; you can just use %SIZE, since it is just a maximum size.

    But for specific instances, creating a CL can make more sense than QCMDEXC; if you want to create a file if it doesn't exist, you can check the existance, and clear the file if it exists, and create it if it does not, and stick it all in one routine you call from multiple locations. Most QCMDEXC in RPG are these simple things that happen over and over.


    Lynne.

    ReplyDelete
  7. With all of you mentioning the QCAPCMD API I will make a post about it in the near future.

    ReplyDelete
  8. Good technique, thank you. One thing I've run across a number of times is the habit the iSeries has of mucking up long character parameters passed to or from programs on calls. A character variable greater than 32 characters as the final parameter (particularly when much greater than 32 characters) where the parameter has trailing blanks can get clobbered with some of the next part of the buffer used in passing parameters. For that reason, I've made a habit of passing trimmed values and particularly passing the trimmed length to QCMDEXC avoids this occasional problem. In this case, the free format call to QCMDEXC would look like the following:

    DExecCmd PR extpgm("QCMDEXC")
    D Commandvar 1024
    D Commandlen 14 5

    callp(e) ExecCmd(%trim(commandvar):%length(%trim(commandvar)))

    In this case it wouldn't make a difference as the length parameter will define the correct end point in the buffer for the 1024 character command being passed, but the habit helps a lot in avoiding this occasional trap calling another program.

    ReplyDelete
  9. IBM i never 'mucks up' parameters. If the caller and callee agree on the parameter definition, parameter passing works 100% of the time. When the definitions differ, the programmer has made a mistake and the parameters pay the price. The classic example is calling from a command line. What is the size of a character literal typed on the command line? It defaults to 32 bytes unless more characters than that are specified between the quotes, in which case the character count specified determines the storage for the literal. If the callee defines the parameter as say, 50a, then the command line passes a pointer to 32 properly formatted characters. When the callee then tries to use characters 33 - 50, what's in them is raw, uninitialised storage.

    ReplyDelete
  10. I use this kind of solution, but not in free-form ILE/RPG, in a customer's solution since 2007. It helps me to use OVRDBF before the explicit open or to recover some errors if there are some objects lacking and recreate them.

    ReplyDelete
  11. Hi Anyone suggest how to trap error from FTP Logfile using RPGLE ?

    ReplyDelete
    Replies
    1. This link is not working for me. Can you please check Simon. Thanks in advance.

      Delete
    2. IBM has moved to new versions of their documentation web site twice since I wrote this post, and they never redirect the old URLs to the new ones.

      I have updated the links with the latest ones.

      Delete
  12. I coded using full-free and I noticed prototype parameter CmdString needs to be declared as char and CmdLen as packed. Varchar and zoned won't work, although char with options(*varsize) does.

    ReplyDelete
    Replies
    1. All the code I publish has been tested on the release I give in the post.

      Delete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.