Handling the _IO_READ message

Updated: October 26, 2022

The io_read handler is responsible for returning data bytes to the client after receiving an _IO_READ message. Examples of functions that send this message are read(), readdir(), fread(), and fgetc(). Let's start by looking at the format of the message itself:

struct _io_read {
    uint16_t            type;
    uint16_t            combine_len;
    uint32_t            nbytes;
    uint32_t            xtype;
    uint32_t            zero;
};

struct _io_read64 {
    uint16_t            type;
    uint16_t            combine_len;
    uint32_t            nbytes;
    uint32_t            xtype;
    uint32_t            nbytes_hi;
};

typedef union {
    struct _io_read     i;
    struct _io_read     i64;
    /* unsigned char    data[nbytes];    */
    /* nbytes is returned with MsgReply  */
} io_read_t;

As with all resource manager messages, we've defined a union that contains the input (coming into the resource manager) structure and a reply or output (going back to the client) structure. The io_read handler is prototyped with an argument of io_read_t *msg—that's the pointer to the union containing the message.

Since this is a read(), the type member has the value _IO_READ or _IO_READ64. The client library uses the _IO_READ64 form only when the length is greater than 4 GB. The items of interest in the input structure are:

combine_len
This field has meaning for a combine message—see the Combine Messages chapter.
nbytes
How many bytes the client is expecting. For an _IO_READ64 message, the high 32 bits of the length are in nbytes_hi.
xtype
A per-message override, if your resource manager supports it. Even if your resource manager doesn't support it, you should still examine this member. More on the xtype later (see the Handling the xtype member section).
nbytes_hi
(_IO_READ64 only) The high 32 bits of the length.
Note: You can use the _IO_READ_GET_NBYTES() macro (defined in <sys/iofunc.h>) to determine the number of bytes. You pass it a pointer to the message:
num_bytes = _IO_READ_GET_NBYTES(msg);

We'll create an io_read handler that actually returns some data (the fixed string "Hello, world\n"). We'll use the OCB to keep track of our position within the buffer that we're returning to the client.

When we get the _IO_READ message, the nbytes member tells us exactly how many bytes the client wants to read. Suppose that the client issues:

read (fd, buf, 4096);

In this case, it's a simple matter to return our entire "Hello, world\n" string in the output buffer and tell the client that we're returning 13 bytes, i.e., the size of the string.

However, consider the case where the client is performing the following:

while (read (fd, &character, 1) != EOF) {
    printf ("Got a character \"%c\"\n", character);
}

Granted, this isn't a terribly efficient way for the client to perform reads! In this case, we would get msg->i.nbytes set to 1 (the size of the buffer that the client wants to get). We can't simply return the entire string all at once to the client—we have to hand it out one character at a time. This is where the OCB's offset member comes into play.