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 <axboe@kernel.dk>
diff --git a/engines/net.c b/engines/net.c
index d6821a4..3401039 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 @@
 	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 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_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 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_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_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 @@
 		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_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 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 @@
 	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_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 @@
 	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_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;
-
-	*sep = '\0';
-	sep++;
-	strcpy(host, buf);
-	if (!strlen(host))
-		goto bad_host;
-
-	modep = NULL;
-	portp = sep;
-	sep = strchr(portp, ',');
-	if (sep) {
-		*sep = '\0';
-		modep = sep + 1;
+	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;
 	}
 
-	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->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);
+	}
+
+	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 (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 @@
 {
 	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 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);