Updated: April 19, 2023 |
An I/O message is one that relies on an existing binding (e.g., OCB) between the client and the resource manager.
As an example, an _IO_READ (from the client's read() function) message depends on the client's having previously established an association (or context) with the resource manager by issuing an open() and getting back a file descriptor. This context, created by the open() call, is then used to process the subsequent I/O messages, like the _IO_READ.
There are good reasons for this design. It would be inefficient to pass the full pathname for each and every read() request, for example. The open() handler can also perform tasks that we want done only once (e.g., permission checks), rather than with each I/O message. Also, when the read() has read 4096 bytes from a disk file, there may be another 20 megabytes still waiting to be read. Therefore, the read() function would need to have some context information telling it the position within the file it's reading from, how much has been read, and so on.
The resmgr_io_funcs_t structure (which you pass to resmgr_attach() along with the connect functions) defines the functions to call for the I/O messages. The resmgr_io_funcs_t structure is defined in <sys/resmgr.h> as shown below.
typedef struct _resmgr_io_funcs { unsigned nfuncs; int (*read) (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb); int (*write) (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb); int (*close_ocb) (resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb); int (*stat) (resmgr_context_t *ctp, io_stat_t *msg, RESMGR_OCB_T *ocb); int (*notify) (resmgr_context_t *ctp, io_notify_t *msg, RESMGR_OCB_T *ocb); int (*devctl) (resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb); int (*unblock) (resmgr_context_t *ctp, io_pulse_t *msg, RESMGR_OCB_T *ocb); int (*pathconf) (resmgr_context_t *ctp, io_pathconf_t *msg, RESMGR_OCB_T *ocb); int (*lseek) (resmgr_context_t *ctp, io_lseek_t *msg, RESMGR_OCB_T *ocb); int (*chmod) (resmgr_context_t *ctp, io_chmod_t *msg, RESMGR_OCB_T *ocb); int (*chown) (resmgr_context_t *ctp, io_chown_t *msg, RESMGR_OCB_T *ocb); int (*utime) (resmgr_context_t *ctp, io_utime_t *msg, RESMGR_OCB_T *ocb); int (*openfd) (resmgr_context_t *ctp, io_openfd_t *msg, RESMGR_OCB_T *ocb); int (*fdinfo) (resmgr_context_t *ctp, io_fdinfo_t *msg, RESMGR_OCB_T *ocb); int (*lock) (resmgr_context_t *ctp, io_lock_t *msg, RESMGR_OCB_T *ocb); int (*space) (resmgr_context_t *ctp, io_space_t *msg, RESMGR_OCB_T *ocb); int (*shutdown) (resmgr_context_t *ctp, io_shutdown_t *msg, RESMGR_OCB_T *ocb); int (*mmap) (resmgr_context_t *ctp, io_mmap_t *msg, RESMGR_OCB_T *ocb); int (*msg) (resmgr_context_t *ctp, io_msg_t *msg, RESMGR_OCB_T *ocb); int (*reserved) (resmgr_context_t *ctp, void *msg, RESMGR_OCB_T *ocb); int (*dup) (resmgr_context_t *ctp, io_dup_t *msg, RESMGR_OCB_T *ocb); int (*close_dup) (resmgr_context_t *ctp, io_close_t *msg, RESMGR_OCB_T *ocb); int (*lock_ocb) (resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb); int (*unlock_ocb) (resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb); int (*sync) (resmgr_context_t *ctp, io_sync_t *msg, RESMGR_OCB_T *ocb); int (*power) (resmgr_context_t *ctp, io_power_t *msg, RESMGR_OCB_T *ocb); int (*acl) (resmgr_context_t *ctp, io_acl_t *msg, RESMGR_OCB_T *ocb); int (*pause) (resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb); int (*unpause) (resmgr_context_t *ctp, io_pulse_t *msg, RESMGR_OCB_T *ocb); int (*read64) (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb); int (*write64) (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb); int (*notify64) (resmgr_context_t *ctp, io_notify_t *msg, RESMGR_OCB_T *ocb); int (*utime64) (resmgr_context_t *ctp, io_utime_t *msg, RESMGR_OCB_T *ocb); } resmgr_io_funcs_t;
You initialize this structure in the same way as the resmgr_connect_funcs_t structure: call iofunc_func_init() to fill it with pointers to the default handlers, and then override any that your resource manager needs to handle specifically. This structure also begins with an nfuncs member that indicates how many functions are in the structure, to allow for future expansion.
Notice that the I/O functions all have a common parameter list. The first entry is a resource manager context structure, the second is a message (the type of which matches the message being handled and contains parameters sent from the client), and the last is an OCB (containing what we bound when we handled the client's open() function).
You usually have to provide a handler for the following entries:
The read64 handler is for large reads. The message type is _IO_READ64, and the message uses a previously unused member to specify the high 32 bits of length.
The write64 handler is for large writes. The message type is _IO_WRITE64, and the message uses a previously unused member to specify the high 32 bits of length.
You typically use the default entry for the following: