XIO: User's Guide

Overview
User Quick Start
>XIO Driver Quick Start

XIO Driver Quick Start Guide

This Guide explains how to create a transport driver for Globus XIO. For the purpose of exploring both what a transform driver is and how to write one this guide will walk through an example driver. The full source code for the driver can be found at Driver Example. This example implements a file driver for globus_xio. If a user of globus_xio were to put this file at the bottom of the stack, they could access files on the local file system.

Data Structures

There are three data structures that will be explored in this example: attribute, target, and handle. The driver defines the memory layout of these data structures but the globus_xio framework imposes certain semantics upon them. It is up to the driver how to use them, but globus_xio will be expecting certain behaviors.

Attributes

Each driver may have its own attribute structure. The attribute gives the globus_xio user API an opportunity to tweak parameters inside the driver. The single attribute structure is used for all possible driver specific attributes:

  1. Target attributes
  2. Handle attributes
  3. Server attributes

How each of these can use the attribute structure will be unveiled as the tutorial continues. For now it is simply important to remember there is attribute structure used to initiate of the driver ADTs.

A driver is not required to have an attribute support at all. However if the driver author chooses to support attributes the following functions must be implemented:

typedef globus_result_t
(*globus_xio_driver_attr_init_t)(
    void **                                     out_attr);

typedef globus_result_t
(*globus_xio_driver_attr_cntl_t)(
    void *                                      attr,
    int                                         cmd,
    va_list                                     ap);

typedef globus_result_t
(*globus_xio_driver_attr_copy_t)(
    void **                                     dst,
    void *                                      src);

typedef globus_result_t
(*globus_xio_driver_attr_destroy_t)(
    void *                                      attr);

See driver API doc for more information.

We shall now take our first look at the file driver example. The file driver needs a way to provide the user level programmer with a means of setting the mode and flags when a file is open (akin to the POSIX function open()).

The first step in creating this ability is to a) define the attribute structure and b) implement the globus_xio_driver_attr_init_t function which will initialize it:

/*
 *  attribute structure 
 */ 
struct globus_l_xio_file_attr_s
{
    int                                         mode;
    int                                         flags;
}

globus_result_t
globus_xio_driver_file_attr_init(
    void **                                     out_attr)
{
    struct globus_l_xio_file_attr_s *           file_attr;
    
    /*
     *  create a file attr structure and initialize its values
     */
    file_attr = (struct globus_l_xio_file_attr_s *)
        globus_malloc(sizeof(struct globus_l_xio_file_attr_s));

    file_attr->flags = O_CREAT;
    file_attr->mode = S_IRWXU;

    /* set the out parameter to the driver attr */
    *out_attr = file_attr;

    return GLOBUS_SUCCESS;
} 

The above simply defines a structure that can hold two integers, mode and flags, then defines a function the will allocate and initialize this structure.
globus_xio hides much of the memory management of these attribute structures from the driver. However, it does need the driver to provide a means of copying them, and free all resources associated with them. In the case of the file driver example, these are both simple:

globus_result_t
globus_xio_driver_file_attr_copy(
    void **                                     dst,
    void *                                      src)
{
    struct globus_l_xio_file_attr_s *           file_attr;

    file_attr = (struct globus_l_xio_file_attr_s *)
        globus_malloc(sizeof(struct globus_l_xio_file_attr_s));

    memcpy(file_attr, src, sizeof(struct globus_l_xio_file_attr_s));
    
    *dst = file_attr;

    return GLOBUS_SUCCESS;
}

globus_result_t
globus_xio_driver_file_attr_destroy(
    void *                                      attr)
{
    globus_free(attr);
    
    return GLOBUS_SUCCESS;
}

 

The above code should be fairly clear. Obviously we need a method with which the user can set flags and mode. This is accomplished with the following interface function:

globus_result_t
globus_xio_driver_file_attr_cntl(
    void *                                      attr,
    int                                         cmd,
    va_list                                     ap)
{   
    struct globus_l_xio_file_attr_s *           file_attr;
    int *                                       out_i;
    
    file_attr = (struct globus_l_xio_file_attr_s *)attr;
    switch(cmd) 
    {
        case GLOBUS_XIO_FILE_SET_MODE:
            file_attr->mode = va_arg(ap, int);
            break;

        case GLOBUS_XIO_FILE_GET_MODE:
            out_i = va_arg(ap, int *);
            *out_i = file_attr->mode;
            break; 

        case GLOBUS_XIO_FILE_SET_FLAGS:
            file_attr->flags = va_arg(ap, int);
            break;

        case GLOBUS_XIO_FILE_GET_FLAGS:
            out_i = va_arg(ap, int *);
            *out_i = file_attr->flags;
            break;

        default:
            return FILE_DRIVER_ERROR_COMMAND_NOT_FOUND;
            break;
    }

    return GLOBUS_SUCCESS;
}

This function is called passing the driver an initialized file_attr structure, a command, and a variable argument list.

Based on the value of cmd, the driver decides to do one of the following:

  • set flags or mode from the va_args
  • return flags or mode to the user via a pointer in va_args.

Target

A target structure represents what a driver will open. It is initialized from a contact string and an attribute. In the case of a file driver, the target simply holds onto the contact string as a path to the file.

The file driver implements the following target functions:

globus_result_t
globus_xio_driver_file_target_init(
    void **                                     out_target,
    void *                                      target_attr,
    const char *                                contact_string,
    globus_xio_driver_stack_t                   stack)
{
    struct globus_l_xio_file_target_s *         target;

    /* create the target structure and copy the contact string into it */
    target = (struct globus_l_xio_file_target_s *)
                globus_malloc(sizeof(struct globus_l_xio_file_target_s));
    strncpy(target->pathname, contact_string, sizeof(target->pathname) - 1);
    target->pathname[sizeof(target->pathname) - 1] = '\0';

    return GLOBUS_SUCCESS;
}

/*
 *  destroy the target structure
 */
globus_result_t
globus_xio_driver_file_target_destroy(
    void *                                      target)
{
    globus_free(target);

    return GLOBUS_SUCCESS;
} 

The above function handles the creation and destruction of the file driver's target structure.

Note: When the target is created, the contact string is copied into it. It is invalid to just copy the pointer to the contact string. As soon as this interface function returns, that pointer is no longer valid.

Handle

The most interesting of the three data types discussed here is the handle. All typical IO operations (open, close, read, write) are performed on the handle. The handle is the initialized form of the target and an attr. The driver developer should use this ADT to keep track of any state information they will need in order to perform reads and writes.

In the example case, the driver handle is fairly simple as the driver is merely a wrapper around POSIX calls.

struct globus_l_xio_file_handle_s
{
    int                                         fd;
};

The reader should review the following functions in Driver Example in order to see how the handle structure is used:

  • globus_xio_driver_file_open()
  • globus_xio_driver_file_write()
  • globus_xio_driver_file_read()
  • globus_xio_driver_file_close()

IO Operations

The read and write interface functions are called in response to a user read or write request. Both functions are provided with a vector that has at least the same members as the 'struct iovec' and a vector length. As of now, the iovec elements may contain extra members, so if you wish to use readv() or writev(), you will have to transfer the iov_base and iov_len members to the POSIX iovec.

As with the open and close interface functions, if an error occurs before any real processing has occurred, the interface function may simply return the error (in a result_t), effectively canceling the operation. However, once bytes have been read or written, you must not return the error. You must report the number of bytes read/written along with the result.

When an operation is done, either by error or successful completion, the operation must be 'finished'. To do this, a call must be made to :

globus_result_t
globus_xio_driver_finished_read/write(
  globus_xio_driver_operation_t         op, 
  globus_result_t                       res, 
  globus_ssize_t                        nbytes);

Blocking vs Non-blocking calls

In general, the driver developer does not need to concern himself with how the user made the call. Whether it was a blocking or an asynchronous call, XIO will handle things correctly.

However the call was made, the driver developer can call globus_xio_driver_finished_{open, read, write, close} either while in the original interface call, in a separate thread, or in a separate callback kick out via the globus_callback API.

The Glue

Through a process not finalized yet, XIO will request the globus_xio_driver_t structure from the driver. This structure defines all of the interface functions that the driver supports. In detail:

    /*
     *  main io interface functions
     */
    globus_xio_driver_open_t                            open_func;
    globus_xio_driver_close_t                           close_func;
    globus_xio_driver_read_t                            read_func;
    globus_xio_driver_write_t                           write_func;
    globus_xio_driver_handle_cntl_t                     handle_cntl_func;

    globus_xio_driver_target_init_t                     target_init_func;
    globus_xio_driver_target_destroy_t                  target_destroy_finc;

    /*
     * target init functions.  Must have client or server
     */
    globus_xio_driver_server_init_t                     server_init_func;
    globus_xio_driver_server_accept_t                   server_accept_func;
    globus_xio_driver_server_destroy_t                  server_destroy_func;
    globus_xio_driver_server_cntl_t                     server_cntl_func;

    /*
     *  driver attr functions.  All or none may be NULL
     */
    globus_xio_driver_attr_init_t                       attr_init_func;
    globus_xio_driver_attr_copy_t                       attr_copy_func;
    globus_xio_driver_attr_cntl_t                       attr_cntl_func;
    globus_xio_driver_attr_destroy_t                    attr_destroy_func;
    
    /*
     *  data descriptor functions.  All or none
     */
    globus_xio_driver_data_descriptor_init_t            dd_init;  
    globus_xio_driver_driver_data_descriptor_copy_t     dd_copy;
    globus_xio_driver_driver_data_descriptor_destroy_t  dd_destroy;
    globus_xio_driver_driver_data_descriptor_cntl_t     dd_cntl;