From de890a1e48d40238dac69f302708dde8719de240 Mon Sep 17 00:00:00 2001 From: Steven Lang Date: Wed, 9 Nov 2011 14:03:34 +0100 Subject: [PATCH 1/1] Private parameters for ioengines 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 --- HOWTO | 64 ++++++++--- README | 2 + engines/libaio.c | 48 ++++++--- engines/net.c | 270 ++++++++++++++++++++++++++++------------------- fio.1 | 72 ++++++++++--- fio.h | 13 ++- init.c | 124 +++++++++++++++++++--- ioengine.h | 7 +- ioengines.c | 66 +++++++++++- options.c | 179 ++++++++++++++++++++----------- parse.c | 58 +++++----- parse.h | 2 +- 12 files changed, 637 insertions(+), 268 deletions(-) diff --git a/HOWTO b/HOWTO index 41edcf1a..2403a5cd 100644 --- 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 b2fdd0ba..03e6751c 100644 --- 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 diff --git a/engines/libaio.c b/engines/libaio.c index ad34d065..e4869aa9 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -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 */ diff --git a/engines/net.c b/engines/net.c index d6821a40..3401039a 100644 --- a/engines/net.c +++ b/engines/net.c @@ -22,15 +22,19 @@ 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 aae76572..138208f2 100644 --- 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 a5405e37..cc1f65f5 100644 --- 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 01e43718..482ce099 100644 --- 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': diff --git a/ioengine.h b/ioengine.h index 044c4da1..be74b66f 100644 --- a/ioengine.h +++ b/ioengine.h @@ -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 */ diff --git a/ioengines.c b/ioengines.c index 7f4e104e..e8ed871d 100644 --- a/ioengines.c +++ b/ioengines.c @@ -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; +} diff --git a/options.c b/options.c index 84bf5ac3..6352f0a8 100644 --- 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 7f7851cb..9a65e313 100644 --- 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 091923e3..4edf75e5 100644 --- 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 *); -- 2.25.1