Methods of returning and replying

You can return to the resource manager library from your handler functions in various ways. This is complicated by the fact that the resource manager library can reply for you if you want it to, but you must tell it to do so and put the information that it'll use in all the right places.

In this section, we'll discuss the following ways of returning to the resource manager library.

Returning with an error

To reply to the client such that the function the client is calling (e.g., read()) will return with an error, you simply return with an appropriate errno value (from <errno.h>).

return (ENOMEM);

In the case of a read(), this causes the read to return -1 with errno set to ENOMEM.

Note: You might sometimes see this in the code for a resource manager:
_RESMGR_ERRNO (error_code)

but this is the same as simply returning the error_code directly. The _RESMGR_ERRNO() macro is deprecated.

Returning using an IOV array that points to your data

Sometimes you'll want to reply with a header followed by one of N buffers, where the buffer used will differ each time you reply. To do this, you can set up an IOV array whose elements point to the header and to a buffer.

The context structure already has an IOV array. If you want the resource manager library to do your reply for you, then you must use this array. But the array must contain enough elements for your needs. To ensure that this is the case, set the nparts_max member of the resmgr_attr_t structure that you passed to resmgr_attach() when you registered your name in the pathname space.

The following example assumes that the variable i contains the offset into the array of buffers of the desired buffer to reply with. The 2 in _RESMGR_NPARTS(2) tells the library how many elements in ctp->iov to reply with.

my_header_t     header;
a_buffer_t      buffers[N];

...

SETIOV(&ctp->iov[0], &header, sizeof(header));
SETIOV(&ctp->iov[1], &buffers[i], sizeof(buffers[i]));
return (_RESMGR_NPARTS(2));

Returning with a single buffer containing data

An example of this would be replying to a read() where all the data existed in a single buffer. You'll typically see this done in two ways:

return (_RESMGR_PTR(ctp, buffer, nbytes));

And:

SETIOV (ctp->iov, buffer, nbytes);
return (_RESMGR_NPARTS(1));

The first method, using the _RESMGR_PTR() macro, is just a convenience for the second method where a single IOV is returned.

Returning success but with no data

This can be done in a few ways. The most simple would be:

return (EOK);

But you'll often see:

return (_RESMGR_NPARTS(0));

Note that in neither case are you causing the MsgSend() to return with a 0. The value that the MsgSend() returns is the value passed to the _IO_SET_READ_NBYTES(), _IO_SET_WRITE_NBYTES(), and other similar macros. These two were used in the read and write samples above.

Getting the resource manager library to do the reply

In this case, you give the client the data and get the resource manager library to do the reply for you. However, the reply data won't be valid by that time. For example, if the reply data was in a buffer that you wanted to free before returning, you could use the following:

resmgr_msgwrite (ctp, buffer, nbytes, 0);
free (buffer);
return (EOK);

The resmgr_msgwrite() function copies the contents of buffer into the client's reply buffer immediately. Note that a reply is still required in order to unblock the client so it can examine the data. Next we free the buffer. Finally, we return to the resource manager library such that it does a reply with zero-length data. Since the reply is of zero length, it doesn't overwrite the data already written into the client's reply buffer. When the client returns from its send call, the data is there waiting for it.

Performing the reply in the server

In all of the previous examples, it's the resource manager library that calls MsgReply*() or MsgError() to unblock the client. In some cases, you may not want the library to reply for you. For instance, you might have already done the reply yourself, or you'll reply later. In either case, you'd return as follows:

return (_RESMGR_NOREPLY);

Leaving the client blocked, replying later

An example of a resource manager that would reply to clients later is a pipe resource manager. If the client is doing a read of your pipe but you have no data for the client, then you have a choice:

Another example might be if the client wants you to write to some device but doesn't want to get a reply until the data has been fully written out. Here's the sequence of events that might follow:

  1. Your resource manager does some I/O to the hardware to tell it that data is available.
  2. The hardware generates an interrupt when it's ready for a packet of data.
  3. You handle the interrupt by writing data out to the hardware.
  4. Many interrupts may occur before all the data is written—only then would you reply to the client.

The first issue, though, is whether the client wants to be left blocked. If the client doesn't want to be left blocked, then it opens with the O_NONBLOCK flag:

fd = open("/dev/sample", O_RDWR | O_NONBLOCK);

The default is to allow you to block it.

One of the first things done in the read and write samples above was to call some POSIX verification functions: iofunc_read_verify() and iofunc_write_verify(). If we pass the address of an int as the last parameter, then on return the functions will stuff that int with nonzero if the client doesn't want to be blocked (O_NONBLOCK flag was set) or with zero if the client wants to be blocked.

int    nonblock;                                                     
                                                                     
if ((status = iofunc_read_verify (ctp, msg, ocb,
                                  &nonblock)) != EOK) 
    return (status);                                                 
                                                                     
...                                                                  
                                                                     
int    nonblock;                                                     
                                                                     
if ((status = iofunc_write_verify (ctp, msg, ocb,
                                   &nonblock)) != EOK)
    return (status);

When it then comes time to decide if we should reply with an error or reply later, we do:

if (nonblock) {
    /* client doesn't want to be blocked */
    return (EAGAIN);                                          
} else {                                                      
    /*
    *  The client is willing to be blocked.
    *  Save at least the ctp->rcvid so that you can
    *  reply to it later.
    */
    ...
    return (_RESMGR_NOREPLY);
}                                                             

The question remains: How do you do the reply yourself? The only detail to be aware of is that the rcvid to reply to is ctp->rcvid. If you're replying later, then you'd save ctp->rcvid and use the saved value in your reply:

MsgReply(saved_rcvid, 0, buffer, nbytes);

Or:

iov_t    iov[2];

SETIOV(&iov[0], &header, sizeof(header));
SETIOV(&iov[1], &buffers[i], sizeof(buffers[i]));
MsgReplyv(saved_rcvid, 0, iov, 2);

Note that you can fill up the client's reply buffer as data becomes available by using resmgr_msgwrite() and resmgr_msgwritev(). Just remember to do the MsgReply*() at some time to unblock the client.

Note: If you're replying to an _IO_READ or _IO_WRITE message, the status argument for MsgReply*() must be the number of bytes read or written.

There's another way to resume the blocked operation, but it isn't as efficient as the other methods: you can call resmgr_msg_again(). This function restores the resmgr_context_t structure to the way it was when your resource manager received the message associated with the rcvid, and then processes the message again as if it had just been received.

Note: If your resource manager leaves clients blocked, you'll need to keep track of which clients are blocked, so that you can unblock them if necessary. For more information, see Unblocking if someone closes a file descriptor in the Unblocking Clients and Handling Interrupts chapter.

Returning and telling the library to do the default action

The default action in most cases is for the library to cause the client's function to fail with ENOSYS:

return (_RESMGR_DEFAULT);