4.2. improvement #2: error handling

The next improvement that our client program certainly needs is the ability to give more specific and meaningful error messages when an API call does not work. This section explains how error handling works in the UNIX-type APIs and explains how to use this in your RPG programs.

In C, there is a global variable called 'errno' that is set to an error number whenever a procedure has an error. Almost all C procedures return either an integer or a pointer, therefore, by convention, when an error occurs a C procedure will return either '-1' or 'NULL'. The programmer will use this as an indication that he needs to check the value of the 'errno' global variable to see which error occurred. (Although most C procedures follow this convention, some don't. Check the manual page for the particular procedure before handling the error)

The 'errno' variable is an integer. It can only contain a number, which is usually different on different platforms. To make things more compatible across platforms, constants are placed into a C 'header file', and programmers are encouraged to use constants when checking for errors.

As an example, lets go back to the IBM manual page for the socket() API. For convienience, it can be found here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/socket.htm

Page down to the section entitled 'Error Conditions', it states: When socket() fails, errno can be set to one of the following:

The proceeds to list things like "EACCES", "EAFNOSUPPORT", "EIO", "EMFILE", along with descriptions of what errors they refer to. Each one of these is actually a constant defined in in a C header file that is translated by the compiler into a number.

In fact, if you have the System Openness Includes licensed program installed, you can bring up the ERRNO member of the file called SYS in library QSYSINC and you'll be able to see how this is done in C programs. EACCESS corresponds to the number 3401, EMFILE to 3452, etc. Unfortunately, some of these are not specifically "UNIX-Type API" errors, but are generic C language errors. So, for those values, you also need to look at the member called ERRNO in the file H in library QCLE. This file is part of the ILE C/400 licensed program, and is only available if you have that licpgm installed.

If you are fortunate enough to have the file 'H' in 'QCLE' installed on your machine, you'll also see the following definition the end:

         #define errno (*__errno())
     

This is a macro that runs at compile time that changes the word 'errno' wherever it's used in a C program, to actually be '(*__errno())'. This is very useful to us because it means that whenever a C program refers to the global variable 'errno', it's actually referring to the return value from a procedure called '__errno()'. That means, if we want to retrieve errno in an RPG program, all we have to do is call that __errno() function ourselves!

For your convienience (and, for mine!) I've defined all of the C errno values from both QCLE and QSYSINC into a single RPG /copy member. I also used some RPG compiler directives to include an errno procedure that will make it much more intuitive to use errno in our RPG programs.

You can get a copy of this /copy member (called errno_h) at this link: http://www.scottklement.com/rpg/socktut/qrpglesrc.errno_h

At first glance, you might be wondering "Why didn't you simply make this a part of the existing socket_h member?" The reason is quite simple: These error routines are used for all UNIX-Type APIs, not just sockets. And, in fact, these routines are also used when calling any function from the ILE C runtime libraries. Consequently, there are many times when we'll want to use the ERRNO_H header member when we aren't working with sockets at all.

To use this, you need to /copy the member twice. Once where you need the definitions of the constants (i.e., in your D-specs), and once where you need the errno procedure (i.e. at the end of the program).

Here's a quick code snippet:

         H BNDDIR('QC2LE')
    
         D/copy qrpglesrc,socket_h
         D/copy qrpglesrc,errno_h
    
            .... more code is found here ....
    
         c                   if        socket(AF_INET:SOCK_STREAM:IPPROTO_IP)<0
         c
         c                   select
         c                   when      errno = EACCES
         c                   eval      Msg = 'Permission Denied'
    
         c                   when      errno = EAFNOSUPPORT
         c                   eval      Msg = 'Address Family not supported!'
    
         c                   when      errno = EIO
         c                   goto      TryAgainMaybe
    
            ... etc, etc, etc ...
    
         c                   endsl
    
            .... more code is found here ....
    
          /define ERRNO_LOAD_PROCEDURE
          /copy libsor/qrpglesrc,errno_h
     

Note that because '__errno()' is part of the C-runtime library (as opposed to being part of the UNIX-Type APIs) we need to include the QC2LE binding directory when compiling our program. Otherwise, it won't find the API.

You see that in this example, the program is trying again (maybe) when it gets an I/O error. For the other errors, it's simply assigning a human-readable error message that could later be used to tell the user and/or programmer what the problem was.

As you can imagine, it can get quite tedious to try to have the program handle every possible error that can occur. Especially when, in most cases, all we do is assign the human-readble error message to a variable. Fortunately, there is already a procedure we can call from the C runtime library that will return a human-readable error message for any errno value.

This procedure is called 'strerror' ("string error") and like all C procedures that return strings, it returns a pointer to a an area of memory containing a string. The string is variable length, and we know when we've reached the end of the string because we encounter a 'NULL' character (x'00'). As we've done before, we can use the RPG built-in-function "%str" to decode this string for us.

So, to call strerror from an RPG program (the prototype is already defined in that /copy member I mentioned earlier) we can simply do something like this:

         H BNDDIR('QC2LE')
    
         D/copy qrpglesrc,socket_h
         D/copy qrpglesrc,errno_h
    
            .... more code is found here ....
    
         c                   if        socket(AF_INET:SOCK_STREAM:IPPROTO_IP)<0
         c
         c                   if        errno = EIO
         c                   goto      TryAgainMaybe
         c                   else
         c                   eval      Msg = %str(strerror(errno))
         c                   endif
         c
         c                   dsply                   Msg
         c
         c                   endif
    
            .... more code is found here ....
    
          /define ERRNO_LOAD_PROCEDURE
          /copy libsor/qrpglesrc,errno_h
     

This will return the human readable error message for everything except for the 'EIO' error. For EIO, it'll go back and 'Try Again Maybe'.