Extending the POSIX-Layer Data Structures

This chapter includes:

Overview

The iofunc_*() default functions operate on the assumption that you've used the default definitions for the context block and the attributes structures. This is a safe assumption for two reasons:

  1. The default context and attribute structures contain sufficient information for most applications.
  2. If the default structures don't hold enough information, you can encapsulate them within the structures that you've defined.

The default structures must be the first members of their respective superstructures, so that the iofunc_*() default functions can access them:


Encapsulated and encapsulating structures


Encapsulating the POSIX-layer data structures.

Extending the OCB and attribute structures

In our /dev/sample example, we had a static buffer associated with the entire resource. Sometimes you may want to keep a pointer to a buffer associated with the resource, rather than in a global area. To maintain the pointer with the resource, we would have to store it in the iofunc_attr_t attribute structure. Since the attribute structure doesn't have any spare fields, we would have to extend it to contain that pointer.

Sometimes you may want to add extra entries to the standard iofunc_*() OCB (iofunc_ocb_t).

Let's see how we can extend both of these structures. The basic strategy used is to encapsulate the existing attributes and OCB structures within a newly defined superstructure that also contains our extensions. Here's the code (see the text following the listing for comments):

/* Define our overrides before including <sys/iofunc.h>  */
struct device;
#define IOFUNC_ATTR_T       struct device  /* see note 1 */
struct ocb;
#define IOFUNC_OCB_T        struct ocb     /* see note 1 */

#include <sys/iofunc.h>
#include <sys/dispatch.h>

struct ocb {                               /* see note 2 */
    iofunc_ocb_t            hdr;           /* see note 4; must always be first */
    struct ocb              *next;
    struct ocb              **prev;        /* see note 3 */
};

struct device {                            /* see note 2 */
    iofunc_attr_t           attr;          /* must always be first */
    struct ocb              *list;         /* waiting for write */
};

/* Prototypes, needed since we refer to them a few lines down */

struct ocb *ocb_calloc (resmgr_context_t *ctp, struct device *device);
void ocb_free (struct ocb *ocb);

iofunc_funcs_t ocb_funcs = { /* our ocb allocating & freeing functions */
    _IOFUNC_NFUNCS,
    ocb_calloc,
    ocb_free
};

/* The mount structure.  We have only one, so we statically declare it */

iofunc_mount_t          mountpoint = { 0, 0, 0, 0, &ocb_funcs };

/* One struct device per attached name (there's only one name in this
   example) */

struct device           deviceattr;

main()
{
    ...

    /* 
     *  deviceattr will indirectly contain the addresses 
     *  of the OCB allocating and freeing functions
     */

    deviceattr.attr.mount = &mountpoint;
    resmgr_attach (..., &deviceattr);

    ...
}

/*
 * ocb_calloc
 *
 *  The purpose of this is to give us a place to allocate our own OCB.
 *  It is called as a result of the open being done
 *  (e.g. iofunc_open_default causes it to be called). We
 *  registered it through the mount structure.
 */
IOFUNC_OCB_T
ocb_calloc (resmgr_context_t *ctp, IOFUNC_ATTR_T *device)
{
    struct ocb *ocb;

    if (!(ocb = calloc (1, sizeof (*ocb)))) {
        return 0;
    }

    /* see note 3 */
    ocb -> prev = &device -> list;
    if (ocb -> next = device -> list) {
        device -> list -> prev = &ocb -> next;
    }
    device -> list = ocb;
    
    return (ocb);
}

/*
 * ocb_free
 *
 * The purpose of this is to give us a place to free our OCB.
 * It is called as a result of the close being done
 * (e.g. iofunc_close_ocb_default causes it to be called). We
 * registered it through the mount structure.
 */
void
ocb_free (IOFUNC_OCB_T *ocb)
{
    /* see note 3 */
    if (*ocb -> prev = ocb -> next) {
        ocb -> next -> prev = ocb -> prev;
        }
        free (ocb);
}

Here are the notes for the above code:

  1. We place the definitions for our enhanced structures before including the standard I/O functions header file. Because the standard I/O functions header file checks to see if the two manifest constants are already defined, this allows a convenient way for us to semantically override the structures.
  2. Define our new enhanced data structures, being sure to place the encapsulated members first.
  3. The ocb_calloc() and ocb_free() sample functions shown here cause the newly allocated OCBs to be maintained in a linked list. Note the use of dual indirection on the struct ocb **prev; member.
  4. You must always place the iofunc structure that you're overriding as the first member of the new extended structure. This lets the common library work properly in the default cases.

Extending the mount structure

You can also extend the iofunc_mount_t structure in the same manner as the attribute and OCB structures. In this case, you'd define:

#define IOFUNC_MOUNT_T       struct newmount  

and then declare the new structure:

struct newmount {
    iofunc_mount_t          mount;
    int                   ourflag;
};