6.6. The fcntl() API call

The previous topic explained how non-blocking mode works. This topic describes the API call that can be used to turn non-blocking mode on for a given socket. This API is capable of setting many other options as well, though they are mostly options that relate to stream files, not sockets, and are not covered by this tutorial.

The fcntl (file control) API is used to retrieve or set the flags that are associrated with a socket or stream file. The IBM manual page for the fcntl() API (when used on a socket) can be found here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/sfcntl.htm

The C-language prototype for the fcntl API looks like this:

           int fcntl(int descriptor, int command, ...);

It starts out pretty simple. We know that the API is called fcntl, and we know that the return value is an integer. We can tell that the first two parameters are both integers, as well. But what the heck is '...'?

The '...' parameter is a special construct in C to denote that the number of parameters remaining is variable. It furthermore means that the types of each of the remaining parameters can also vary. In short, in addition to the two integers, we can pass any number of additional parameters to the fcntl() API, and they can be of any data type. Not very helpful, is it?

Reading more of the manual page, however, we see that some of the values for the 'command' parameter of fcntl() accept an integer as the 3rd parameter. Other values for the 'command' parameter don't require a 3rd parameter at all. Since we now know that the '...' can only symbolize either an integer or nothing, we can write the prototype in RPG like this:

         D fcntl           PR            10I 0 ExtProc('fcntl')
         D   SocketDesc                  10I 0 Value
         D   Command                     10I 0 Value
         D   Arg                         10I 0 Value Options(*NOPASS)

But, since fcntl can also be used for stream files (much like close() can) we will use some compiler directives to prevent it being defined twice, should we want to use the socket APIs in the same program as the IFS (stream file) APIs. So, we'll do this:

         D/if not defined(FCNTL_PROTOTYPE)
         D fcntl           PR            10I 0 ExtProc('fcntl')
         D   SocketDesc                  10I 0 Value
         D   Command                     10I 0 Value
         D   Arg                         10I 0 Value Options(*NOPASS)
         D/define FCNTL_PROTOTYPE

And, of course, add that to the SOCKET_H header file that we've been working on.

In addition, we'll need constants to define the possible values for the 'command' parameter of fcntl(). For the sake of being able to turn on the "non-blocking" flag, we're only concerned with two constants. 'F_GETFL' and 'F_SETFL' which are used to get and set the status flags, respectively.

These can be added to our SOCKET_H header file by typing this:

         ** fcntl() commands
         D F_GETFL         C                   CONST(6)
         D F_SETFL         C                   CONST(7)

The O_NONBLOCK flag also needs a constant so that we don't have to try to remember which flag is used for blocking/non-blocking :) The page in the manual tells us that 'O_NONBLOCK', 'O_NDELAY' and 'FNDELAY' all do the same thing, so we'll just define them all, like this:

         ** fcntl() flags
         D O_NONBLOCK      C                   CONST(128)
         D O_NDELAY        C                   CONST(128)
         D FNDELAY         C                   CONST(128)

So... all of that should now be in your SOCKET_H member. Of course, if you simply downloaded my copy of SOCKET_H, it was already there :)

To call fcntl() to put a socket in non-blocking mode, you'll do something like this:

         ***** retrieve flags
         c                   eval      flags = fcntl(sock: F_GETFL)
         c                   if        flags < 0
         C* handle error "unable to retrieve descriptor status flags"
         c                   endif
         ***** turn non non-blocking flag:
         c                   eval      flags = flags + O_NONBLOCK
         ***** set flags
         c                   if        fcntl(sock: F_SETFL: flags) < 0
         C* handle error "unable to set descriptor status flags"
         c                   endif