The read I/O function handler example was fairly simple; let's take a look at the write I/O function handler. The major hurdle to overcome with the write I/O function handler is to access the data. Since the resource manager library reads in a small portion of the message from the client, the data content that the client sent (immediately after the _IO_WRITE header) may have only partially arrived at the write I/O function handler. For example, for a client writing one megabyte, only the header and up to 1500 bytes of the data get read by the resource manager library.
You use resmgr_msgread() to get any data that exceeds the 1500 or so bytes that are available from the message buffer. However, you can simplify the process and reduce the likelihood of bugs by ignoring the data in the message buffer and using resmgr_msgread() to get all the data. Making use of the data from the message buffer might improve performance, but you are unlikely to notice any improvement unless your resource manager deals with a very large number of write operations that contain a small amount of data. The best way to improve performance is for clients to perform larger writes.
One further wrinkle introduced in the write I/O function handler example is the handling of the _IO_XTYPE_OFFSET modifier (and associated data; it's done slightly differently than in the read I/O function handler example).
Here's the code:
/* * io_write1.c */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/neutrino.h> #include <sys/iofunc.h> void process_data (int offet, void *buffer, int nbytes) { // do something with the data } int io_write (resmgr_context_t *ctp, io_write_t *msg, iofunc_ocb_t *ocb) { int sts; size_t nbytes; size_t off; size_t start_data_offset; int xtype; char *buffer; struct _xtype_offset *xoffset; // verify that the device is opened for write if ((sts = iofunc_write_verify (ctp, msg, ocb, NULL)) != EOK) { return (sts); } // 1) check for and handle an XTYPE override xtype = msg -> i.xtype & _IO_XTYPE_MASK; if (xtype == _IO_XTYPE_OFFSET) { xoffset = (struct _xtype_offset *) (&msg -> i + 1); start_data_offset = sizeof (msg -> i) + sizeof (*xoffset); off = xoffset -> offset; } else if (xtype == _IO_XTYPE_NONE) { off = ocb -> offset; start_data_offset = sizeof (msg -> i); } else { // unknown, fail it return (ENOSYS); } // 2) allocate a buffer big enough for the data nbytes = _IO_WRITE_GET_NBYTES(msg); if ((buffer = malloc (nbytes)) == NULL) { return (ENOMEM); } // 3) (re-)read the data from the client if (resmgr_msgread (ctp, buffer, nbytes, start_data_offset) == -1) { free (buffer); return (errno); } // 4) do something with the data process_data (off, buffer, nbytes); // 5) free the buffer free (buffer); // 6) set up the number of bytes for the client's "write" // function to return _IO_SET_WRITE_NBYTES (ctp, nbytes); // 7) if any data written, update POSIX structures and OCB offset if (nbytes) { ocb -> attr -> flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_DIRTY_TIME; if (xtype == _IO_XTYPE_NONE) { ocb -> offset += nbytes; } } // 8) tell the resource manager library to do the reply, and that it // was okay return (EOK); }
As you can see, a few of the initial operations performed were identical to those done in the read I/O function handler example—the iofunc_write_verify() is analogous to the iofunc_read_verify() function, and the xtype override check is the same.
The parameters to resmgr_msgread() are fairly straightforward; we give it the internal context pointer (ctp), the buffer into which we want the data placed (buffer), and the number of bytes that we wish read (the nbytes member of the message msg union). The last parameter is the offset into the current message, which we calculated above, in step 1. The offset effectively skips the header information that the client's C library implementation of write() put there, and proceeds directly to the data. This actually brings about two interesting points: