Private parameters for ioengines
authorSteven Lang <tirea@google.com>
Wed, 9 Nov 2011 13:03:34 +0000 (14:03 +0100)
committerJens Axboe <axboe@kernel.dk>
Wed, 9 Nov 2011 13:03:34 +0000 (14:03 +0100)
Here is the polished version of the engine private options patch.  As
discussed, the global section only ever tracks the private options for
the globally defined ioengine.  For command line parameters, the
ioengine must be selected before any private options are used.  (IE
--ioengine=libaio --userspace_reap will work, but --userspace_reap
--ioengine=libaio will not.)

The userspace_reap option from libaio has been moved over to this new
option method, usage should be identical to before.
The net ioengine has been modified to use parameters, with hostname,
port, protocol and listen defined as ioengine private parameters.  The
old style of hostname=host,port,protocol no longer works, so usage
will need to be updated.  (It will spit out an error that should be
clear enough that it changed if this is tried.)  Also, with the new
way for specifying parameters, the net IO engine now allows data to
flow in either direction on TCP connections, regardless of which end
initiates the connection.

There's also a new command line argument --enghelp which can be used
to get help on ioengine private parameters, similar to --cmdhelp.
With no argument, it lists all built-in ioengine.  The argument is an
ioengine name (Or path to .so) and optionally a comma followed by a
command name, which behaves identically to --cmdhelp.

For ioengine authorship, if options are supplied, both the options
structure and the size of the storage needed must be supplied, and the
storage must be large enough to hold a pointer to struct thread_data;
that is because the options callback doesn't explicitly have a pointer
to the thread data (Normally it relies on the fact that the options
struct is the start of the thread data), so the offset 0 of the struct
must point to the thread data, and is filled in automatically.  (This
also neatly provides a guarantee that offset 0 is reserved in the
options data, so it can be safely used as a test of undefined.)

Signed-off-by: Jens Axboe <axboe@kernel.dk>
12 files changed:
HOWTO
README
engines/libaio.c
engines/net.c
fio.1
fio.h
init.c
ioengine.h
ioengines.c
options.c
parse.c
parse.h

diff --git a/HOWTO b/HOWTO
index 41edcf1..2403a5c 100644 (file)
--- a/HOWTO
+++ b/HOWTO
@@ -524,16 +524,7 @@ ioengine=str       Defines how the job issues io to the file. The following
                        libaio  Linux native asynchronous io. Note that Linux
                                may only support queued behaviour with
                                non-buffered IO (set direct=1 or buffered=0).
-                               This engine also has a sub-option,
-                               userspace_reap. To set it, use
-                               ioengine=libaio:userspace_reap. Normally, with
-                               the libaio engine in use, fio will use the
-                               io_getevents system call to reap newly returned
-                               events. With this flag turned on, the AIO ring
-                               will be read directly from user-space to reap
-                               events. The reaping mode is only enabled when
-                               polling for a minimum of 0 events (eg when
-                               iodepth_batch_complete=0).
+                               This engine defines engine specific options.
 
                        posixaio glibc posix asynchronous io.
 
@@ -562,16 +553,16 @@ ioengine=str      Defines how the job issues io to the file. The following
                                itself and for debugging/testing purposes.
 
                        net     Transfer over the network to given host:port.
-                               'filename' must be set appropriately to
-                               filename=host/port/protocol regardless of send
-                               or receive, if the latter only the port
-                               argument is used. 'host' may be an IP address
-                               or hostname, port is the port number to be used,
-                               and protocol may be 'udp' or 'tcp'. If no
-                               protocol is given, TCP is used.
+                               Depending on the protocol used, the hostname,
+                               port, listen and filename options are used to
+                               specify what sort of connection to make, while
+                               the protocol option determines which protocol
+                               will be used.
+                               This engine defines engine specific options.
 
                        netsplice Like net, but uses splice/vmsplice to
                                map data and send/receive.
+                               This engine defines engine specific options.
 
                        cpuio   Doesn't transfer any data, but burns CPU
                                cycles according to the cpuload= and
@@ -1210,6 +1201,45 @@ uid=int          Instead of running as the invoking user, set the user ID to
 
 gid=int                Set group ID, see uid.
 
+In addition, there are some parameters which are only valid when a specific
+ioengine is in use. These are used identically to normal parameters, with the
+caveat that when used on the command line, they must come after the ioengine
+that defines them is selected.
+
+[libaio] userspace_reap Normally, with the libaio engine in use, fio will use
+               the io_getevents system call to reap newly returned events.
+               With this flag turned on, the AIO ring will be read directly
+               from user-space to reap events. The reaping mode is only
+               enabled when polling for a minimum of 0 events (eg when
+               iodepth_batch_complete=0).
+
+[netsplice] hostname=str
+[net] hostname=str The host name or IP address to use for TCP or UDP based IO.
+               If the job is a TCP listener or UDP reader, the hostname is not
+               used and must be omitted.
+
+[netsplice] port=int
+[net] port=int The TCP or UDP port to bind to or connect to.
+
+[netsplice] protocol=str
+[netsplice] proto=str
+[net] protocol=str
+[net] proto=str        The network protocol to use. Accepted values are:
+
+                       tcp     Transmission control protocol
+                       udp     Unreliable datagram protocol
+                       unix    UNIX domain socket
+
+               When the protocol is TCP or UDP, the port must also be given,
+               as well as the hostname if the job is a TCP listener or UDP
+               reader. For unix sockets, the normal filename option should be
+               used and the port is invalid.
+
+[net] listen   For TCP network connections, tell fio to listen for incoming
+               connections rather than initiating an outgoing connection. The
+               hostname must be omitted if this option is used.
+
+
 6.0 Interpreting the output
 ---------------------------
 
diff --git a/README b/README
index b2fdd0b..03e6751 100644 (file)
--- a/README
+++ b/README
@@ -140,6 +140,8 @@ $ fio
        --terse-version=type    Terse version output format (default 3, or 2).
        --help                  Print this page
        --cmdhelp=cmd           Print command help, "all" for all of them
+       --enghelp=engine        Print ioengine help, or list available ioengines
+       --enghelp=engine,cmd    Print help for an ioengine cmd
        --showcmd               Turn a job file into command line options
        --readonly              Turn on safety read-only checks, preventing
                                writes
index ad34d06..e4869aa 100644 (file)
@@ -24,6 +24,23 @@ struct libaio_data {
        int iocbs_nr;
 };
 
+struct libaio_options {
+       struct thread_data *td;
+       unsigned int userspace_reap;
+};
+
+static struct fio_option options[] = {
+       {
+               .name   = "userspace_reap",
+               .type   = FIO_OPT_STR_SET,
+               .off1   = offsetof(struct libaio_options, userspace_reap),
+               .help   = "Use alternative user-space reap implementation",
+       },
+       {
+               .name   = NULL,
+       },
+};
+
 static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u)
 {
        struct fio_file *f = io_u->file;
@@ -103,11 +120,12 @@ static int fio_libaio_getevents(struct thread_data *td, unsigned int min,
                                unsigned int max, struct timespec *t)
 {
        struct libaio_data *ld = td->io_ops->data;
+       struct libaio_options *o = td->eo;
        unsigned actual_min = td->o.iodepth_batch_complete == 0 ? 0 : min;
        int r, events = 0;
 
        do {
-               if (td->o.userspace_libaio_reap == 1
+               if (o->userspace_reap == 1
                    && actual_min == 0
                    && ((struct aio_ring *)(ld->aio_ctx))->magic
                                == AIO_RING_MAGIC) {
@@ -262,19 +280,21 @@ static int fio_libaio_init(struct thread_data *td)
 }
 
 static struct ioengine_ops ioengine = {
-       .name           = "libaio",
-       .version        = FIO_IOOPS_VERSION,
-       .init           = fio_libaio_init,
-       .prep           = fio_libaio_prep,
-       .queue          = fio_libaio_queue,
-       .commit         = fio_libaio_commit,
-       .cancel         = fio_libaio_cancel,
-       .getevents      = fio_libaio_getevents,
-       .event          = fio_libaio_event,
-       .cleanup        = fio_libaio_cleanup,
-       .open_file      = generic_open_file,
-       .close_file     = generic_close_file,
-       .get_file_size  = generic_get_file_size,
+       .name                   = "libaio",
+       .version                = FIO_IOOPS_VERSION,
+       .init                   = fio_libaio_init,
+       .prep                   = fio_libaio_prep,
+       .queue                  = fio_libaio_queue,
+       .commit                 = fio_libaio_commit,
+       .cancel                 = fio_libaio_cancel,
+       .getevents              = fio_libaio_getevents,
+       .event                  = fio_libaio_event,
+       .cleanup                = fio_libaio_cleanup,
+       .open_file              = generic_open_file,
+       .close_file             = generic_close_file,
+       .get_file_size          = generic_get_file_size,
+       .options                = options,
+       .option_struct_size     = sizeof(struct libaio_options),
 };
 
 #else /* FIO_HAVE_LIBAIO */
index d6821a4..3401039 100644 (file)
 
 struct netio_data {
        int listenfd;
-       int send_to_net;
        int use_splice;
-       int type;
        int pipes[2];
-       char host[64];
        struct sockaddr_in addr;
        struct sockaddr_un addr_un;
 };
 
+struct netio_options {
+       struct thread_data *td;
+       unsigned int port;
+       unsigned int proto;
+       unsigned int listen;
+};
+
 struct udp_close_msg {
        uint32_t magic;
        uint32_t cmd;
@@ -45,6 +49,55 @@ enum {
        FIO_TYPE_UNIX   = 3,
 };
 
+static int str_hostname_cb(void *data, const char *input);
+static struct fio_option options[] = {
+       {
+               .name   = "hostname",
+               .type   = FIO_OPT_STR_STORE,
+               .cb     = str_hostname_cb,
+               .help   = "Hostname for net IO engine",
+       },
+       {
+               .name   = "port",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct netio_options, port),
+               .minval = 1,
+               .maxval = 65535,
+               .help   = "Port to use for TCP or UDP net connections",
+       },
+       {
+               .name   = "protocol",
+               .alias  = "proto",
+               .type   = FIO_OPT_STR,
+               .off1   = offsetof(struct netio_options, proto),
+               .help   = "Network protocol to use",
+               .def    = "tcp",
+               .posval = {
+                         { .ival = "tcp",
+                           .oval = FIO_TYPE_TCP,
+                           .help = "Transmission Control Protocol",
+                         },
+                         { .ival = "udp",
+                           .oval = FIO_TYPE_UDP,
+                           .help = "Unreliable Datagram Protocol",
+                         },
+                         { .ival = "unix",
+                           .oval = FIO_TYPE_UNIX,
+                           .help = "UNIX domain socket",
+                         },
+               },
+       },
+       {
+               .name   = "listen",
+               .type   = FIO_OPT_STR_SET,
+               .off1   = offsetof(struct netio_options, listen),
+               .help   = "Listen for incoming TCP connections",
+       },
+       {
+               .name   = NULL,
+       },
+};
+
 /*
  * Return -1 for error and 'nr events' for a positive number
  * of events
@@ -78,13 +131,16 @@ static int poll_wait(struct thread_data *td, int fd, short events)
 
 static int fio_netio_prep(struct thread_data *td, struct io_u *io_u)
 {
-       struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
 
        /*
         * Make sure we don't see spurious reads to a receiver, and vice versa
         */
-       if ((nd->send_to_net && io_u->ddir == DDIR_READ) ||
-           (!nd->send_to_net && io_u->ddir == DDIR_WRITE)) {
+       if (o->proto == FIO_TYPE_TCP)
+               return 0;
+
+       if ((o->listen && io_u->ddir == DDIR_WRITE) ||
+           (!o->listen && io_u->ddir == DDIR_READ)) {
                td_verror(td, EINVAL, "bad direction");
                return 1;
        }
@@ -230,10 +286,11 @@ static int fio_netio_splice_out(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_send(struct thread_data *td, struct io_u *io_u)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int ret, flags = OS_MSG_DONTWAIT;
 
        do {
-               if (nd->type == FIO_TYPE_UDP) {
+               if (o->proto == FIO_TYPE_UDP) {
                        struct sockaddr *to = (struct sockaddr *) &nd->addr;
 
                        ret = sendto(io_u->file->fd, io_u->xfer_buf,
@@ -283,10 +340,11 @@ static int is_udp_close(struct io_u *io_u, int len)
 static int fio_netio_recv(struct thread_data *td, struct io_u *io_u)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int ret, flags = OS_MSG_DONTWAIT;
 
        do {
-               if (nd->type == FIO_TYPE_UDP) {
+               if (o->proto == FIO_TYPE_UDP) {
                        fio_socklen_t len = sizeof(nd->addr);
                        struct sockaddr *from = (struct sockaddr *) &nd->addr;
 
@@ -316,19 +374,20 @@ static int fio_netio_recv(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int ret;
 
        fio_ro_check(td, io_u);
 
        if (io_u->ddir == DDIR_WRITE) {
-               if (!nd->use_splice || nd->type == FIO_TYPE_UDP ||
-                   nd->type == FIO_TYPE_UNIX) 
+               if (!nd->use_splice || o->proto == FIO_TYPE_UDP ||
+                   o->proto == FIO_TYPE_UNIX)
                        ret = fio_netio_send(td, io_u);
                else
                        ret = fio_netio_splice_out(td, io_u);
        } else if (io_u->ddir == DDIR_READ) {
-               if (!nd->use_splice || nd->type == FIO_TYPE_UDP ||
-                   nd->type == FIO_TYPE_UDP)
+               if (!nd->use_splice || o->proto == FIO_TYPE_UDP ||
+                   o->proto == FIO_TYPE_UNIX)
                        ret = fio_netio_recv(td, io_u);
                else
                        ret = fio_netio_splice_in(td, io_u);
@@ -359,19 +418,20 @@ static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int type, domain;
 
-       if (nd->type == FIO_TYPE_TCP) {
+       if (o->proto == FIO_TYPE_TCP) {
                domain = AF_INET;
                type = SOCK_STREAM;
-       } else if (nd->type == FIO_TYPE_UDP) {
+       } else if (o->proto == FIO_TYPE_UDP) {
                domain = AF_INET;
                type = SOCK_DGRAM;
-       } else if (nd->type == FIO_TYPE_UNIX) {
+       } else if (o->proto == FIO_TYPE_UNIX) {
                domain = AF_UNIX;
                type = SOCK_STREAM;
        } else {
-               log_err("fio: bad network type %d\n", nd->type);
+               log_err("fio: bad network type %d\n", o->proto);
                f->fd = -1;
                return 1;
        }
@@ -382,9 +442,9 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
                return 1;
        }
 
-       if (nd->type == FIO_TYPE_UDP)
+       if (o->proto == FIO_TYPE_UDP)
                return 0;
-       else if (nd->type == FIO_TYPE_TCP) {
+       else if (o->proto == FIO_TYPE_TCP) {
                fio_socklen_t len = sizeof(nd->addr);
 
                if (connect(f->fd, (struct sockaddr *) &nd->addr, len) < 0) {
@@ -411,9 +471,10 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
 static int fio_netio_accept(struct thread_data *td, struct fio_file *f)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        fio_socklen_t socklen = sizeof(nd->addr);
 
-       if (nd->type == FIO_TYPE_UDP) {
+       if (o->proto == FIO_TYPE_UDP) {
                f->fd = nd->listenfd;
                return 0;
        }
@@ -464,13 +525,13 @@ static void fio_netio_udp_close(struct thread_data *td, struct fio_file *f)
 
 static int fio_netio_close_file(struct thread_data *td, struct fio_file *f)
 {
-       struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
 
        /*
         * If this is an UDP connection, notify the receiver that we are
         * closing down the link
         */
-       if (nd->type == FIO_TYPE_UDP)
+       if (o->proto == FIO_TYPE_UDP)
                fio_netio_udp_close(td, f);
 
        return generic_close_file(td, f);
@@ -510,15 +571,14 @@ static int fio_netio_setup_connect_unix(struct thread_data *td,
        return 0;
 }
 
-static int fio_netio_setup_connect(struct thread_data *td, const char *host,
-                                  unsigned short port)
+static int fio_netio_setup_connect(struct thread_data *td)
 {
-       struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
 
-       if (nd->type == FIO_TYPE_UDP || nd->type == FIO_TYPE_TCP)
-               return fio_netio_setup_connect_inet(td, host, port);
+       if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP)
+               return fio_netio_setup_connect_inet(td, td->o.filename,o->port);
        else
-               return fio_netio_setup_connect_unix(td, host);
+               return fio_netio_setup_connect_unix(td, td->o.filename);
 }
 
 static int fio_netio_setup_listen_unix(struct thread_data *td, const char *path)
@@ -557,9 +617,10 @@ static int fio_netio_setup_listen_unix(struct thread_data *td, const char *path)
 static int fio_netio_setup_listen_inet(struct thread_data *td, short port)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int fd, opt, type;
 
-       if (nd->type == FIO_TYPE_TCP)
+       if (o->proto == FIO_TYPE_TCP)
                type = SOCK_STREAM;
        else
                type = SOCK_DGRAM;
@@ -595,20 +656,20 @@ static int fio_netio_setup_listen_inet(struct thread_data *td, short port)
        return 0;
 }
 
-static int fio_netio_setup_listen(struct thread_data *td, const char *path,
-                                 short port)
+static int fio_netio_setup_listen(struct thread_data *td)
 {
        struct netio_data *nd = td->io_ops->data;
+       struct netio_options *o = td->eo;
        int ret;
 
-       if (nd->type == FIO_TYPE_UDP || nd->type == FIO_TYPE_TCP)
-               ret = fio_netio_setup_listen_inet(td, port);
+       if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP)
+               ret = fio_netio_setup_listen_inet(td, o->port);
        else
-               ret = fio_netio_setup_listen_unix(td, path);
+               ret = fio_netio_setup_listen_unix(td, td->o.filename);
 
        if (ret)
                return ret;
-       if (nd->type == FIO_TYPE_UDP)
+       if (o->proto == FIO_TYPE_UDP)
                return 0;
 
        if (listen(nd->listenfd, 10) < 0) {
@@ -622,72 +683,46 @@ static int fio_netio_setup_listen(struct thread_data *td, const char *path,
 
 static int fio_netio_init(struct thread_data *td)
 {
-       struct netio_data *nd = td->io_ops->data;
-       unsigned int port;
-       char host[64], buf[128];
-       char *sep, *portp, *modep;
+       struct netio_options *o = td->eo;
        int ret;
 
-       if (td_rw(td)) {
-               log_err("fio: network connections must be read OR write\n");
-               return 1;
-       }
        if (td_random(td)) {
                log_err("fio: network IO can't be random\n");
                return 1;
        }
 
-       strcpy(buf, td->o.filename);
-
-       sep = strchr(buf, ',');
-       if (!sep)
-               goto bad_host;
+       if (o->proto == FIO_TYPE_UNIX && o->port) {
+               log_err("fio: network IO port not valid with unix socket\n");
+               return 1;
+       } else if (o->proto != FIO_TYPE_UNIX && !o->port) {
+               log_err("fio: network IO requires port for tcp or udp\n");
+               return 1;
+       }
 
-       *sep = '\0';
-       sep++;
-       strcpy(host, buf);
-       if (!strlen(host))
-               goto bad_host;
+       if (o->proto != FIO_TYPE_TCP) {
+               if (o->listen) {
+                         log_err("fio: listen only valid for TCP proto IO\n");
+                         return 1;
+               }
+               if (td_rw(td)) {
+                         log_err("fio: datagram network connections must be"
+                                  " read OR write\n");
+                         return 1;
+               }
+               o->listen = td_read(td);
+       }
 
-       modep = NULL;
-       portp = sep;
-       sep = strchr(portp, ',');
-       if (sep) {
-               *sep = '\0';
-               modep = sep + 1;
+       if (o->proto != FIO_TYPE_UNIX && o->listen && td->o.filename) {
+               log_err("fio: hostname not valid for inbound network IO\n");
+               return 1;
        }
 
-       if (!strncmp("tcp", modep, strlen(modep)) ||
-           !strncmp("TCP", modep, strlen(modep)))
-               nd->type = FIO_TYPE_TCP;
-       else if (!strncmp("udp", modep, strlen(modep)) ||
-                !strncmp("UDP", modep, strlen(modep)))
-               nd->type = FIO_TYPE_UDP;
-       else if (!strncmp("unix", modep, strlen(modep)) ||
-                !strncmp("UNIX", modep, strlen(modep)))
-               nd->type = FIO_TYPE_UNIX;
+       if (o->listen)
+               ret = fio_netio_setup_listen(td);
        else
-               goto bad_host;
-
-       if (nd->type != FIO_TYPE_UNIX) {
-               port = strtol(portp, NULL, 10);
-               if (!port || port > 65535)
-                       goto bad_host;
-       } else
-               port = 0;
-
-       if (td_read(td)) {
-               nd->send_to_net = 0;
-               ret = fio_netio_setup_listen(td, host, port);
-       } else {
-               nd->send_to_net = 1;
-               ret = fio_netio_setup_connect(td, host, port);
-       }
+               ret = fio_netio_setup_connect(td);
 
        return ret;
-bad_host:
-       log_err("fio: bad network host/port/protocol: %s\n", td->o.filename);
-       return 1;
 }
 
 static void fio_netio_cleanup(struct thread_data *td)
@@ -710,6 +745,11 @@ static int fio_netio_setup(struct thread_data *td)
 {
        struct netio_data *nd;
 
+       if (!td->files_index) {
+               add_file(td, td->o.filename ?: "net");
+               td->o.nr_files = td->o.nr_files ?: 1;
+       }
+
        if (!td->io_ops->data) {
                nd = malloc(sizeof(*nd));;
 
@@ -742,34 +782,48 @@ static int fio_netio_setup_splice(struct thread_data *td)
 }
 
 static struct ioengine_ops ioengine_splice = {
-       .name           = "netsplice",
-       .version        = FIO_IOOPS_VERSION,
-       .prep           = fio_netio_prep,
-       .queue          = fio_netio_queue,
-       .setup          = fio_netio_setup_splice,
-       .init           = fio_netio_init,
-       .cleanup        = fio_netio_cleanup,
-       .open_file      = fio_netio_open_file,
-       .close_file     = generic_close_file,
-       .flags          = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
-                         FIO_SIGTERM | FIO_PIPEIO,
+       .name                   = "netsplice",
+       .version                = FIO_IOOPS_VERSION,
+       .prep                   = fio_netio_prep,
+       .queue                  = fio_netio_queue,
+       .setup                  = fio_netio_setup_splice,
+       .init                   = fio_netio_init,
+       .cleanup                = fio_netio_cleanup,
+       .open_file              = fio_netio_open_file,
+       .close_file             = generic_close_file,
+       .options                = options,
+       .option_struct_size     = sizeof(struct netio_options),
+       .flags                  = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
+                                 FIO_SIGTERM | FIO_PIPEIO,
 };
 #endif
 
 static struct ioengine_ops ioengine_rw = {
-       .name           = "net",
-       .version        = FIO_IOOPS_VERSION,
-       .prep           = fio_netio_prep,
-       .queue          = fio_netio_queue,
-       .setup          = fio_netio_setup,
-       .init           = fio_netio_init,
-       .cleanup        = fio_netio_cleanup,
-       .open_file      = fio_netio_open_file,
-       .close_file     = fio_netio_close_file,
-       .flags          = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
-                         FIO_SIGTERM | FIO_PIPEIO,
+       .name                   = "net",
+       .version                = FIO_IOOPS_VERSION,
+       .prep                   = fio_netio_prep,
+       .queue                  = fio_netio_queue,
+       .setup                  = fio_netio_setup,
+       .init                   = fio_netio_init,
+       .cleanup                = fio_netio_cleanup,
+       .open_file              = fio_netio_open_file,
+       .close_file             = fio_netio_close_file,
+       .options                = options,
+       .option_struct_size     = sizeof(struct netio_options),
+       .flags                  = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
+                                 FIO_SIGTERM | FIO_PIPEIO,
 };
 
+static int str_hostname_cb(void *data, const char *input)
+{
+       struct netio_options *o = data;
+
+       if (o->td->o.filename)
+               free(o->td->o.filename);
+       o->td->o.filename = strdup(input);
+       return 0;
+}
+
 static void fio_init fio_netio_register(void)
 {
        register_ioengine(&ioengine_rw);
diff --git a/fio.1 b/fio.1
index aae7657..138208f 100644 (file)
--- a/fio.1
+++ b/fio.1
@@ -44,6 +44,9 @@ Display usage information and exit.
 .BI \-\-cmdhelp \fR=\fPcommand
 Print help information for \fIcommand\fR.  May be `all' for all commands.
 .TP
+.BI \-\-enghelp \fR=\fPioengine[,command]
+List all commands defined by \fIioengine\fR, or print help for \fIcommand\fR defined by \fIioengine\fR.
+.TP
 .BI \-\-showcmd \fR=\fPjobfile
 Convert \fIjobfile\fR to a set of command-line options.
 .TP
@@ -142,9 +145,8 @@ than `./'.
 .B fio
 normally makes up a file name based on the job name, thread number, and file
 number. If you want to share files between threads in a job or several jobs,
-specify a \fIfilename\fR for each of them to override the default. If the I/O
-engine used is `net', \fIfilename\fR is the host and port to connect to in the
-format \fIhost\fR/\fIport\fR. If the I/O engine is file-based, you can specify
+specify a \fIfilename\fR for each of them to override the default.
+If the I/O engine is file-based, you can specify
 a number of files by separating the names with a `:' character. `\-' is a
 reserved name, meaning stdin or stdout, depending on the read/write direction
 set.
@@ -398,13 +400,7 @@ Basic \fIreadv\fR\|(2) or \fIwritev\fR\|(2) I/O. Will emulate queuing by
 coalescing adjacents IOs into a single submission.
 .TP
 .B libaio
-Linux native asynchronous I/O.  This engine also has a sub-option,
-\fBuserspace_reap\fR. To set it, use \fBioengine=libaio:userspace_reap\fR.
-Normally, with the libaio engine in use, fio will use the
-\fIio_getevents\fR\|(3) system call to reap newly returned events. With this
-flag turned on, the AIO ring will be read directly from user-space to reap
-events. The reaping mode is only enabled when polling for a minimum of \fB0\fR
-events (eg when \fBiodepth_batch_complete=0\fR).
+Linux native asynchronous I/O. This ioengine defines engine specific options.
 .TP
 .B posixaio
 POSIX asynchronous I/O using \fIaio_read\fR\|(3) and \fIaio_write\fR\|(3).
@@ -436,14 +432,14 @@ Doesn't transfer any data, just pretends to.  Mainly used to exercise \fBfio\fR
 itself and for debugging and testing purposes.
 .TP
 .B net
-Transfer over the network.  \fBfilename\fR must be set appropriately to
-`\fIhost\fR,\fIport\fR,\fItype\fR' regardless of data direction. \fItype\fR
-is one of \fBtcp\fR, \fBudp\fR, or \fBunix\fR. For UNIX domain sockets,
-the \fIhost\fR parameter is a file system path.
+Transfer over the network.  The protocol to be used can be defined with the
+\fBprotocol\fR parameter.  Depending on the protocol, \fBfilename\fR,
+\fBhostname\fR, \fBport\fR, or \fBlisten\fR must be specified.
+This ioengine defines engine specific options.
 .TP
 .B netsplice
 Like \fBnet\fR, but uses \fIsplice\fR\|(2) and \fIvmsplice\fR\|(2) to map data
-and send/receive.
+and send/receive. This ioengine defines engine specific options.
 .TP
 .B cpuio
 Doesn't transfer any data, but burns CPU cycles according to \fBcpuload\fR and
@@ -965,6 +961,52 @@ the maximum length of the list is 20. Use ':' to separate the
 numbers. For example, \-\-percentile_list=99.5:99.9 will cause fio to
 report the values of completion latency below which 99.5% and 99.9% of
 the observed latencies fell, respectively.
+.SS "Ioengine Parameters List"
+Some parameters are only valid when a specific ioengine is in use. These are
+used identically to normal parameters, with the caveat that when used on the
+command line, the must come after the ioengine that defines them is selected.
+.TP
+.BI (libaio)userspace_reap
+Normally, with the libaio engine in use, fio will use
+the io_getevents system call to reap newly returned events.
+With this flag turned on, the AIO ring will be read directly
+from user-space to reap events. The reaping mode is only
+enabled when polling for a minimum of 0 events (eg when
+iodepth_batch_complete=0).
+.TP
+.BI (net,netsplice)hostname \fR=\fPstr
+The host name or IP address to use for TCP or UDP based IO.
+If the job is a TCP listener or UDP reader, the hostname is not
+used and must be omitted.
+.TP
+.BI (net,netsplice)port \fR=\fPint
+The TCP or UDP port to bind to or connect to.
+.TP
+.BI (net,netsplice)protocol \fR=\fPstr "\fR,\fP proto" \fR=\fPstr
+The network protocol to use. Accepted values are:
+.RS
+.RS
+.TP
+.B tcp
+Transmission control protocol
+.TP
+.B udp
+Unreliable datagram protocol
+.TP
+.B unix
+UNIX domain socket
+.RE
+.P
+When the protocol is TCP or UDP, the port must also be given,
+as well as the hostname if the job is a TCP listener or UDP
+reader. For unix sockets, the normal filename option should be
+used and the port is invalid.
+.RE
+.TP
+.BI (net,netsplice)listen
+For TCP network connections, tell fio to listen for incoming
+connections rather than initiating an outgoing connection. The
+hostname must be omitted if this option is used.
 .SH OUTPUT
 While running, \fBfio\fR will display the status of the created jobs.  For
 example:
diff --git a/fio.h b/fio.h
index a5405e3..cc1f65f 100644 (file)
--- a/fio.h
+++ b/fio.h
@@ -245,8 +245,6 @@ struct thread_options {
        unsigned int gid;
 
        unsigned int sync_file_range;
-
-       unsigned int userspace_libaio_reap;
 };
 
 /*
@@ -254,6 +252,7 @@ struct thread_options {
  */
 struct thread_data {
        struct thread_options o;
+       void *eo;
        char verror[FIO_VERROR_SIZE];
        pthread_t thread;
        int thread_number;
@@ -551,16 +550,20 @@ extern void reset_fio_state(void);
 extern int fio_options_parse(struct thread_data *, char **, int);
 extern void fio_keywords_init(void);
 extern int fio_cmd_option_parse(struct thread_data *, const char *, char *);
+extern int fio_cmd_ioengine_option_parse(struct thread_data *, const char *, char *);
 extern void fio_fill_default_options(struct thread_data *);
 extern int fio_show_option_help(const char *);
+extern void fio_options_set_ioengine_opts(struct option *long_options, struct thread_data *td);
 extern void fio_options_dup_and_init(struct option *);
-extern void options_mem_dupe(struct thread_data *);
-extern void options_mem_free(struct thread_data *);
+extern void fio_options_mem_dupe(struct thread_data *);
+extern void options_mem_dupe(void *data, struct fio_option *options);
 extern void td_fill_rand_seeds(struct thread_data *);
 extern void add_job_opts(const char **);
 extern char *num2str(unsigned long, int, int, int);
+extern int ioengine_load(struct thread_data *);
 
-#define FIO_GETOPT_JOB         0x89988998
+#define FIO_GETOPT_JOB         0x89000000
+#define FIO_GETOPT_IOENGINE    0x98000000
 #define FIO_NR_OPTIONS         (FIO_MAX_OPTS + 128)
 
 /*
diff --git a/init.c b/init.c
index 01e4371..482ce09 100644 (file)
--- a/init.c
+++ b/init.c
@@ -138,6 +138,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
                .has_arg        = optional_argument,
                .val            = 'c' | FIO_CLIENT_FLAG,
        },
+       {
+               .name              = (char *) "enghelp",
+               .has_arg        = optional_argument,
+               .val                = 'i' | FIO_CLIENT_FLAG,
+       },
        {
                .name           = (char *) "showcmd",
                .has_arg        = no_argument,
@@ -278,7 +283,8 @@ static int setup_thread_area(void)
 /*
  * Return a free job structure.
  */
-static struct thread_data *get_new_job(int global, struct thread_data *parent)
+static struct thread_data *get_new_job(int global, struct thread_data *parent,
+                                      int preserve_eo)
 {
        struct thread_data *td;
 
@@ -297,10 +303,14 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent)
        td = &threads[thread_number++];
        *td = *parent;
 
+       td->io_ops = NULL;
+       if (!preserve_eo)
+               td->eo = NULL;
+
        td->o.uid = td->o.gid = -1U;
 
        dup_files(td, parent);
-       options_mem_dupe(td);
+       fio_options_mem_dupe(td);
 
        profile_add_hooks(td);
 
@@ -319,6 +329,8 @@ static void put_job(struct thread_data *td)
                log_info("fio: %s\n", td->verror);
 
        fio_options_free(td);
+       if (td->io_ops)
+               free_ioengine(td);
 
        memset(&threads[td->thread_number - 1], 0, sizeof(*td));
        thread_number--;
@@ -662,6 +674,62 @@ static int init_random_state(struct thread_data *td)
        return 0;
 }
 
+/*
+ * Initializes the ioengine configured for a job, if it has not been done so
+ * already.
+ */
+int ioengine_load(struct thread_data *td)
+{
+       const char *engine;
+
+       /*
+        * Engine has already been loaded.
+        */
+       if (td->io_ops)
+               return 0;
+
+       engine = get_engine_name(td->o.ioengine);
+       td->io_ops = load_ioengine(td, engine);
+       if (!td->io_ops) {
+               log_err("fio: failed to load engine %s\n", engine);
+               return 1;
+       }
+
+       if (td->io_ops->option_struct_size && td->io_ops->options) {
+               /*
+                * In cases where td->eo is set, clone it for a child thread.
+                * This requires that the parent thread has the same ioengine,
+                * but that requirement must be enforced by the code which
+                * cloned the thread.
+                */
+               void *origeo = td->eo;
+               /*
+                * Otherwise use the default thread options.
+                */
+               if (!origeo && td != &def_thread && def_thread.eo &&
+                   def_thread.io_ops->options == td->io_ops->options)
+                       origeo = def_thread.eo;
+
+               options_init(td->io_ops->options);
+               td->eo = malloc(td->io_ops->option_struct_size);
+               /*
+                * Use the default thread as an option template if this uses the
+                * same options structure and there are non-default options
+                * used.
+                */
+               if (origeo) {
+                       memcpy(td->eo, origeo, td->io_ops->option_struct_size);
+                       options_mem_dupe(td->eo, td->io_ops->options);
+               } else {
+                       memset(td->eo, 0, td->io_ops->option_struct_size);
+                       fill_default_options(td->eo, td->io_ops->options);
+               }
+               *(struct thread_data **)td->eo = td;
+       }
+
+       return 0;
+}
+
 /*
  * Adds a job to the list of things todo. Sanitizes the various options
  * to make sure we don't have conflicts, and initializes various
@@ -672,7 +740,6 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
        const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
                                   "randread", "randwrite", "randrw" };
        unsigned int i;
-       const char *engine;
        char fname[PATH_MAX];
        int numjobs, file_alloced;
 
@@ -693,12 +760,8 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
        if (profile_td_init(td))
                goto err;
 
-       engine = get_engine_name(td->o.ioengine);
-       td->io_ops = load_ioengine(td, engine);
-       if (!td->io_ops) {
-               log_err("fio: failed to load engine %s\n", engine);
+       if (ioengine_load(td))
                goto err;
-       }
 
        if (td->o.use_thread)
                nr_thread++;
@@ -726,6 +789,13 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
        if (fixup_options(td))
                goto err;
 
+       /*
+        * IO engines only need this for option callbacks, and the address may
+        * change in subprocesses.
+        */
+       if (td->eo)
+               *(struct thread_data **)td->eo = NULL;
+
        if (td->io_ops->flags & FIO_DISKLESSIO) {
                struct fio_file *f;
 
@@ -812,7 +882,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
         */
        numjobs = td->o.numjobs;
        while (--numjobs) {
-               struct thread_data *td_new = get_new_job(0, td);
+               struct thread_data *td_new = get_new_job(0, td, 1);
 
                if (!td_new)
                        goto err;
@@ -860,11 +930,11 @@ void add_job_opts(const char **o)
                        sprintf(jobname, "%s", o[i] + 5);
                }
                if (in_global && !td_parent)
-                       td_parent = get_new_job(1, &def_thread);
+                       td_parent = get_new_job(1, &def_thread, 0);
                else if (!in_global && !td) {
                        if (!td_parent)
                                td_parent = &def_thread;
-                       td = get_new_job(0, td_parent);
+                       td = get_new_job(0, td_parent, 0);
                }
                if (in_global)
                        fio_options_parse(td_parent, (char **) &o[i], 1);
@@ -999,7 +1069,7 @@ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
                        first_sect = 0;
                }
 
-               td = get_new_job(global, &def_thread);
+               td = get_new_job(global, &def_thread, 0);
                if (!td) {
                        ret = 1;
                        break;
@@ -1118,6 +1188,10 @@ static void usage(const char *name)
        printf("  --help\t\tPrint this page\n");
        printf("  --cmdhelp=cmd\t\tPrint command help, \"all\" for all of"
                " them\n");
+       printf("  --enghelp=engine\tPrint ioengine help, or list"
+               " available ioengines\n");
+       printf("  --enghelp=engine,cmd\tPrint help for an ioengine"
+               " cmd\n");
        printf("  --showcmd\t\tTurn a job file into command line options\n");
        printf("  --eta=when\t\tWhen ETA estimate should be printed\n");
        printf("            \t\tMay be \"always\", \"never\" or \"auto\"\n");
@@ -1316,6 +1390,12 @@ int parse_cmd_line(int argc, char *argv[])
                                do_exit++;
                        }
                        break;
+               case 'i':
+                       if (!cur_client) {
+                               fio_show_ioengine_help(optarg);
+                               do_exit++;
+                       }
+                       break;
                case 's':
                        dump_cmdline = 1;
                        break;
@@ -1385,12 +1465,28 @@ int parse_cmd_line(int argc, char *argv[])
                                if (is_section && skip_this_section(val))
                                        continue;
 
-                               td = get_new_job(global, &def_thread);
-                               if (!td)
+                               td = get_new_job(global, &def_thread, 1);
+                               if (!td || ioengine_load(td))
                                        return 0;
+                               fio_options_set_ioengine_opts(l_opts, td);
                        }
 
                        ret = fio_cmd_option_parse(td, opt, val);
+
+                       if (!ret && !strcmp(opt, "ioengine")) {
+                               free_ioengine(td);
+                               if (ioengine_load(td))
+                                       return 0;
+                               fio_options_set_ioengine_opts(l_opts, td);
+                       }
+                       break;
+               }
+               case FIO_GETOPT_IOENGINE: {
+                       const char *opt = l_opts[lidx].name;
+                       char *val = optarg;
+                       opt = l_opts[lidx].name;
+                       val = optarg;
+                       ret = fio_cmd_ioengine_option_parse(td, opt, val);
                        break;
                }
                case 'w':
index 044c4da..be74b66 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef FIO_IOENGINE_H
 #define FIO_IOENGINE_H
 
-#define FIO_IOOPS_VERSION      12
+#define FIO_IOOPS_VERSION      13
 
 enum {
        IO_U_F_FREE             = 1 << 0,
@@ -121,6 +121,8 @@ struct ioengine_ops {
        int (*open_file)(struct thread_data *, struct fio_file *);
        int (*close_file)(struct thread_data *, struct fio_file *);
        int (*get_file_size)(struct thread_data *, struct fio_file *);
+       int option_struct_size;
+       struct fio_option *options;
        void *data;
        void *dlhandle;
 };
@@ -155,8 +157,11 @@ extern int __must_check td_io_get_file_size(struct thread_data *, struct fio_fil
 extern struct ioengine_ops *load_ioengine(struct thread_data *, const char *);
 extern void register_ioengine(struct ioengine_ops *);
 extern void unregister_ioengine(struct ioengine_ops *);
+extern void free_ioengine(struct thread_data *);
 extern void close_ioengine(struct thread_data *);
 
+extern int fio_show_ioengine_help(const char *engine);
+
 /*
  * io unit handling
  */
index 7f4e104..e8ed871 100644 (file)
@@ -152,13 +152,17 @@ struct ioengine_ops *load_ioengine(struct thread_data *td, const char *name)
        return ret;
 }
 
-void close_ioengine(struct thread_data *td)
+/*
+ * For cleaning up an ioengine which never made it to init().
+ */
+void free_ioengine(struct thread_data *td)
 {
-       dprint(FD_IO, "close ioengine %s\n", td->io_ops->name);
+       dprint(FD_IO, "free ioengine %s\n", td->io_ops->name);
 
-       if (td->io_ops->cleanup) {
-               td->io_ops->cleanup(td);
-               td->io_ops->data = NULL;
+       if (td->eo && td->io_ops->options) {
+               options_free(td->io_ops->options, td->eo);
+               free(td->eo);
+               td->eo = NULL;
        }
 
        if (td->io_ops->dlhandle)
@@ -168,6 +172,18 @@ void close_ioengine(struct thread_data *td)
        td->io_ops = NULL;
 }
 
+void close_ioengine(struct thread_data *td)
+{
+       dprint(FD_IO, "close ioengine %s\n", td->io_ops->name);
+
+       if (td->io_ops->cleanup) {
+               td->io_ops->cleanup(td);
+               td->io_ops->data = NULL;
+       }
+
+       free_ioengine(td);
+}
+
 int td_io_prep(struct thread_data *td, struct io_u *io_u)
 {
        dprint_io_u(io_u, "prep");
@@ -501,3 +517,43 @@ int do_io_u_trim(struct thread_data *td, struct io_u *io_u)
        return 0;
 #endif
 }
+
+int fio_show_ioengine_help(const char *engine)
+{
+       struct flist_head *entry;
+       struct thread_data td;
+       char *sep;
+       int ret = 1;
+
+       if (!engine || !*engine) {
+               log_info("Available IO engines:\n");
+               flist_for_each(entry, &engine_list) {
+                       td.io_ops = flist_entry(entry, struct ioengine_ops,
+                                               list);
+                       log_info("\t%s\n", td.io_ops->name);
+               }
+               return 0;
+       }
+       sep = strchr(engine, ',');
+       if (sep) {
+               *sep = 0;
+               sep++;
+       }
+
+       memset(&td, 0, sizeof(td));
+
+       td.io_ops = load_ioengine(&td, engine);
+       if (!td.io_ops) {
+               log_info("IO engine %s not found\n", engine);
+               return 1;
+       }
+
+       if (td.io_ops->options)
+               ret = show_cmd_help(td.io_ops->options, sep);
+       else
+               log_info("IO engine %s has no options\n", td.io_ops->name);
+
+       free_ioengine(&td);
+
+       return ret;
+}
index 84bf5ac..6352f0a 100644 (file)
--- a/options.c
+++ b/options.c
@@ -226,21 +226,6 @@ static int str_rw_cb(void *data, const char *str)
        return 0;
 }
 
-#ifdef FIO_HAVE_LIBAIO
-static int str_libaio_cb(void *data, const char *str)
-{
-       struct thread_data *td = data;
-
-       if (!strcmp(str, "userspace_reap")) {
-               td->o.userspace_libaio_reap = 1;
-               return 0;
-       }
-
-       log_err("fio: bad libaio sub-option: %s\n", str);
-       return 1;
-}
-#endif
-
 static int str_mem_cb(void *data, const char *mem)
 {
        struct thread_data *td = data;
@@ -595,14 +580,6 @@ static char *get_next_file_name(char **ptr)
        return start;
 }
 
-static int str_hostname_cb(void *data, const char *input)
-{
-       struct thread_data *td = data;
-
-       td->o.filename = strdup(input);
-       return 0;
-}
-
 static int str_filename_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
@@ -880,12 +857,6 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .prio   = -1, /* must come after "directory" */
                .help   = "File(s) to use for the workload",
        },
-       {
-               .name   = "hostname",
-               .type   = FIO_OPT_STR_STORE,
-               .cb     = str_hostname_cb,
-               .help   = "Hostname for net IO engine",
-       },
        {
                .name   = "kb_base",
                .type   = FIO_OPT_INT,
@@ -999,7 +970,6 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #ifdef FIO_HAVE_LIBAIO
                          { .ival = "libaio",
                            .help = "Linux native asynchronous IO",
-                           .cb   = str_libaio_cb,
                          },
 #endif
 #ifdef FIO_HAVE_POSIXAIO
@@ -1015,7 +985,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #ifdef FIO_HAVE_WINDOWSAIO
                          { .ival = "windowsaio",
                            .help = "Windows native asynchronous IO"
-                         },
+                         },
 #endif
                          { .ival = "mmap",
                            .help = "Memory mapped IO"
@@ -2137,33 +2107,26 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 };
 
 static void add_to_lopt(struct option *lopt, struct fio_option *o,
-                       const char *name)
+                       const char *name, int val)
 {
        lopt->name = (char *) name;
-       lopt->val = FIO_GETOPT_JOB;
+       lopt->val = val;
        if (o->type == FIO_OPT_STR_SET)
                lopt->has_arg = no_argument;
        else
                lopt->has_arg = required_argument;
 }
 
-void fio_options_dup_and_init(struct option *long_options)
+static void options_to_lopts(struct fio_option *opts,
+                             struct option *long_options,
+                             int i, int option_type)
 {
-       struct fio_option *o;
-       unsigned int i;
-
-       options_init(options);
-
-       i = 0;
-       while (long_options[i].name)
-               i++;
-
-       o = &options[0];
+       struct fio_option *o = &opts[0];
        while (o->name) {
-               add_to_lopt(&long_options[i], o, o->name);
+               add_to_lopt(&long_options[i], o, o->name, option_type);
                if (o->alias) {
                        i++;
-                       add_to_lopt(&long_options[i], o, o->alias);
+                       add_to_lopt(&long_options[i], o, o->alias, option_type);
                }
 
                i++;
@@ -2172,6 +2135,43 @@ void fio_options_dup_and_init(struct option *long_options)
        }
 }
 
+void fio_options_set_ioengine_opts(struct option *long_options,
+                                  struct thread_data *td)
+{
+       unsigned int i;
+
+       i = 0;
+       while (long_options[i].name) {
+               if (long_options[i].val == FIO_GETOPT_IOENGINE) {
+                       memset(&long_options[i], 0, sizeof(*long_options));
+                       break;
+               }
+               i++;
+       }
+
+       /*
+        * Just clear out the prior ioengine options.
+        */
+       if (!td || !td->eo)
+               return;
+
+       options_to_lopts(td->io_ops->options, long_options, i,
+                        FIO_GETOPT_IOENGINE);
+}
+
+void fio_options_dup_and_init(struct option *long_options)
+{
+       unsigned int i;
+
+       options_init(options);
+
+       i = 0;
+       while (long_options[i].name)
+               i++;
+
+       options_to_lopts(options, long_options, i, FIO_GETOPT_JOB);
+}
+
 struct fio_keyword {
        const char *word;
        const char *desc;
@@ -2389,18 +2389,53 @@ static char **dup_and_sub_options(char **opts, int num_opts)
 
 int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 {
-       int i, ret;
+       int i, ret, unknown;
        char **opts_copy;
 
        sort_options(opts, options, num_opts);
        opts_copy = dup_and_sub_options(opts, num_opts);
 
-       for (ret = 0, i = 0; i < num_opts; i++) {
-               ret |= parse_option(opts_copy[i], opts[i], options, td);
+       for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) {
+               struct fio_option *o;
+               int newret = parse_option(opts_copy[i], opts[i], options, &o,
+                                         td);
 
-               if (opts_copy[i])
+               if (opts_copy[i]) {
+                       if (newret && !o) {
+                               unknown++;
+                               continue;
+                       }
                        free(opts_copy[i]);
-               opts_copy[i] = NULL;
+                       opts_copy[i] = NULL;
+               }
+
+               ret |= newret;
+       }
+
+       if (unknown) {
+               ret |= ioengine_load(td);
+               if (td->eo) {
+                       sort_options(opts_copy, td->io_ops->options, num_opts);
+                       opts = opts_copy;
+               }
+               for (i = 0; i < num_opts; i++) {
+                       struct fio_option *o = NULL;
+                       int newret = 1;
+                       if (!opts_copy[i])
+                               continue;
+
+                       if (td->eo)
+                               newret = parse_option(opts_copy[i], opts[i],
+                                                     td->io_ops->options, &o,
+                                                     td->eo);
+
+                       ret |= newret;
+                       if (!o)
+                               log_err("Bad option <%s>\n", opts[i]);
+
+                       free(opts_copy[i]);
+                       opts_copy[i] = NULL;
+               }
        }
 
        free(opts_copy);
@@ -2412,6 +2447,12 @@ int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val)
        return parse_cmd_option(opt, val, options, td);
 }
 
+int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
+                               char *val)
+{
+       return parse_cmd_option(opt, val, td->io_ops->options, td);
+}
+
 void fio_fill_default_options(struct thread_data *td)
 {
        fill_default_options(td, options);
@@ -2422,28 +2463,35 @@ int fio_show_option_help(const char *opt)
        return show_cmd_help(options, opt);
 }
 
-/*
- * dupe FIO_OPT_STR_STORE options
- */
-void options_mem_dupe(struct thread_data *td)
+void options_mem_dupe(void *data, struct fio_option *options)
 {
-       struct thread_options *o = &td->o;
-       struct fio_option *opt;
+       struct fio_option *o;
        char **ptr;
-       int i;
 
-       for (i = 0, opt = &options[0]; opt->name; i++, opt = &options[i]) {
-               if (opt->type != FIO_OPT_STR_STORE)
+       for (o = &options[0]; o->name; o++) {
+               if (o->type != FIO_OPT_STR_STORE)
                        continue;
 
-               ptr = (void *) o + opt->off1;
-               if (!*ptr)
-                       ptr = td_var(o, opt->off1);
+               ptr = td_var(data, o->off1);
                if (*ptr)
                        *ptr = strdup(*ptr);
        }
 }
 
+/*
+ * dupe FIO_OPT_STR_STORE options
+ */
+void fio_options_mem_dupe(struct thread_data *td)
+{
+       options_mem_dupe(&td->o, options);
+       if (td->eo) {
+               void *oldeo = td->eo;
+               td->eo = malloc(td->io_ops->option_struct_size);
+               memcpy(td->eo, oldeo, td->io_ops->option_struct_size);
+               options_mem_dupe(td->eo, td->io_ops->options);
+       }
+}
+
 unsigned int fio_get_kb_base(void *data)
 {
        struct thread_data *td = data;
@@ -2528,4 +2576,9 @@ void del_opt_posval(const char *optname, const char *ival)
 void fio_options_free(struct thread_data *td)
 {
        options_free(options, td);
+       if (td->eo && td->io_ops && td->io_ops->options) {
+               options_free(td->io_ops->options, td->eo);
+               free(td->eo);
+               td->eo = NULL;
+       }
 }
diff --git a/parse.c b/parse.c
index 7f7851c..9a65e31 100644 (file)
--- a/parse.c
+++ b/parse.c
@@ -503,7 +503,7 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
 
                posval_sort(o, posval);
 
-               if (!o->posval[0].ival) {
+               if ((o->roff1 || o->off1) && !o->posval[0].ival) {
                        vp = NULL;
                        goto match;
                }
@@ -728,7 +728,7 @@ static int handle_option(struct fio_option *o, const char *__ptr, void *data)
        return ret;
 }
 
-static struct fio_option *get_option(const char *opt,
+static struct fio_option *get_option(char *opt,
                                     struct fio_option *options, char **post)
 {
        struct fio_option *o;
@@ -738,7 +738,7 @@ static struct fio_option *get_option(const char *opt,
        if (ret) {
                *post = ret;
                *ret = '\0';
-               ret = (char *) opt;
+               ret = opt;
                (*post)++;
                strip_blank_end(ret);
                o = find_option(options, ret);
@@ -752,24 +752,27 @@ static struct fio_option *get_option(const char *opt,
 
 static int opt_cmp(const void *p1, const void *p2)
 {
-       struct fio_option *o1, *o2;
-       char *s1, *s2, *foo;
+       struct fio_option *o;
+       char *s, *foo;
        int prio1, prio2;
 
-       s1 = strdup(*((char **) p1));
-       s2 = strdup(*((char **) p2));
-
-       o1 = get_option(s1, fio_options, &foo);
-       o2 = get_option(s2, fio_options, &foo);
-
        prio1 = prio2 = 0;
-       if (o1)
-               prio1 = o1->prio;
-       if (o2)
-               prio2 = o2->prio;
 
-       free(s1);
-       free(s2);
+       if (*(char **)p1) {
+               s = strdup(*((char **) p1));
+               o = get_option(s, fio_options, &foo);
+               if (o)
+                       prio1 = o->prio;
+               free(s);
+       }
+       if (*(char **)p2) {
+               s = strdup(*((char **) p2));
+               o = get_option(s, fio_options, &foo);
+               if (o)
+                       prio2 = o->prio;
+               free(s);
+       }
+
        return prio2 - prio1;
 }
 
@@ -798,24 +801,29 @@ int parse_cmd_option(const char *opt, const char *val,
        return 1;
 }
 
-int parse_option(const char *opt, const char *input,
-                struct fio_option *options, void *data)
+int parse_option(char *opt, const char *input,
+                struct fio_option *options, struct fio_option **o, void *data)
 {
-       struct fio_option *o;
        char *post;
 
        if (!opt) {
                log_err("fio: failed parsing %s\n", input);
+               *o = NULL;
                return 1;
        }
 
-       o = get_option(opt, options, &post);
-       if (!o) {
-               log_err("Bad option <%s>\n", input);
+       *o = get_option(opt, options, &post);
+       if (!*o) {
+               if (post) {
+                       int len = strlen(opt);
+                       if (opt + len + 1 != post)
+                               memmove(opt + len + 1, post, strlen(post));
+                       opt[len] = '=';
+               }
                return 1;
        }
 
-       if (!handle_option(o, post, data)) {
+       if (!handle_option(*o, post, data)) {
                return 0;
        }
 
@@ -1053,7 +1061,7 @@ void options_free(struct fio_option *options, void *data)
        dprint(FD_PARSE, "free options\n");
 
        for (o = &options[0]; o->name; o++) {
-               if (o->type != FIO_OPT_STR_STORE)
+               if (o->type != FIO_OPT_STR_STORE || !o->off1)
                        continue;
 
                ptr = td_var(data, o->off1);
diff --git a/parse.h b/parse.h
index 091923e..4edf75e 100644 (file)
--- a/parse.h
+++ b/parse.h
@@ -65,7 +65,7 @@ struct fio_option {
 
 typedef int (str_cb_fn)(void *, char *);
 
-extern int parse_option(const char *, const char *, struct fio_option *, void *);
+extern int parse_option(char *, const char *, struct fio_option *, struct fio_option **, void *);
 extern void sort_options(char **, struct fio_option *, int);
 extern int parse_cmd_option(const char *t, const char *l, struct fio_option *, void *);
 extern int show_cmd_help(struct fio_option *, const char *);