Sample code for handling _IO_READ messages
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
static char *buffer = "Hello world\n";
static resmgr_connect_funcs_t connect_funcs;
static resmgr_io_funcs_t io_funcs;
static iofunc_attr_t attr;
int main(int argc, char **argv)
{
/* declare variables we'll be using */
resmgr_attr_t resmgr_attr;
dispatch_t *dpp;
dispatch_context_t *ctp;
int id;
/* initialize dispatch interface */
if((dpp = dispatch_create()) == NULL) {
fprintf(stderr, "%s: Unable to allocate dispatch handle.\n", argv[0]);
return EXIT_FAILURE;
}
/* initialize resource manager attributes */
memset(&resmgr_attr, 0, sizeof resmgr_attr);
resmgr_attr.nparts_max = 1;
resmgr_attr.msg_max_size = 2048;
/* initialize functions for handling messages, including our read handlers */
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
io_funcs.read = io_read;
io_funcs.read64 = io_read;
/* initialize attribute structure used by the device */
iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);
attr.nbytes = strlen(buffer)+1;
/* attach our device name */
if((id = resmgr_attach(dpp, &resmgr_attr, "/dev/sample", _FTYPE_ANY, 0,
&connect_funcs, &io_funcs, &attr)) == -1) {
fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
return EXIT_FAILURE;
}
/* allocate a context structure */
ctp = dispatch_context_alloc(dpp);
/* start the resource manager message loop */
while(1) {
if((ctp = dispatch_block(ctp)) == NULL) {
fprintf(stderr, "block error\n");
return EXIT_FAILURE;
}
dispatch_handler(ctp);
}
return EXIT_SUCCESS;
}
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
size_t nleft;
size_t nbytes;
int nparts;
int status;
if ((status = iofunc_read_verify (ctp, msg, ocb, NULL)) != EOK)
return (status);
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
return (ENOSYS);
/*
* On all reads (first and subsequent), calculate how many bytes we can
* return to the client, based upon the number of bytes available (nleft)
* and the client's buffer size
*/
nleft = ocb->attr->nbytes - ocb->offset;
nbytes = min (_IO_READ_GET_NBYTES(msg), nleft);
if (nbytes > 0) {
/* set up the return data IOV */
SETIOV (ctp->iov, buffer + ocb->offset, nbytes);
/* set up the number of bytes (returned by client's read()) */
_IO_SET_READ_NBYTES (ctp, nbytes);
/*
* advance the offset by the number of bytes returned to the client
*/
ocb->offset += nbytes;
nparts = 1;
} else {
/*
* they've asked for zero bytes or they've already previously
* read everything
*/
_IO_SET_READ_NBYTES (ctp, 0);
nparts = 0;
}
/* mark the access time as invalid (we just accessed it) */
if (msg->i.nbytes > 0)
ocb->attr->flags |= IOFUNC_ATTR_ATIME;
return (_RESMGR_NPARTS (nparts));
}
The ocb maintains our context for us by storing the offset field, which gives us the position within the buffer, and by having a pointer to the attribute structure attr, which tells us how big the buffer actually is via its nbytes member.
/* initialize functions for handling messages */
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
io_funcs.read = io_read;
#include <errno.h>
#include <unistd.h>
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
static char *buffer = "Hello world\n";"
iofunc_attr_init (&attr, S_IFNAM | 0666, 0, 0);
we added this one:
attr.nbytes = strlen (buffer)+1;
# cat /dev/sample
Hello, world
The return line (_RESMGR_NPARTS(nparts))
tells the resource manager library to:
- reply to the client for us
- reply with nparts IOVs
Where does it get the IOV array? It's using
ctp->iov
. That's why we first used the
SETIOV()
macro to make ctp->iov
point to the data to reply with.
If we had no data, as would be the case of a read of zero bytes, then we'd do a return
(_RESMGR_NPARTS(0))
. But read()
returns with the number of bytes successfully read. Where
did we give it this information? That's what the
_IO_SET_READ_NBYTES() macro was for. It takes the
nbytes that we give it and stores it in the
context structure (ctp). Then when we return to
the library, the library takes this nbytes and
passes it as the second parameter to the
MsgReplyv(). The second parameter tells the
kernel what the MsgSend() should return. And
since the read() function is calling
MsgSend(), that's where it finds out how many
bytes were read.
We also update the access time for this device in the read handler.
For details on updating the access time, see the section on
Updating the time for reads and writes
below.